Skip to content

Commit

Permalink
feat: store refresh token (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
crlssn authored Nov 11, 2024
1 parent b6cb280 commit ab19e3d
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 92 deletions.
9 changes: 5 additions & 4 deletions db/migrations/002_base.up.sql
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
CREATE TABLE getstronger.auth
(
id UUID PRIMARY KEY NOT NULL DEFAULT uuid_generate_v4(),
email VARCHAR(255) NOT NULL UNIQUE,
password BYTEA NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC')
id UUID PRIMARY KEY NOT NULL DEFAULT uuid_generate_v4(),
email VARCHAR(128) NOT NULL UNIQUE,
password BYTEA NOT NULL,
refresh_token VARCHAR(256) NULL,
created_at TIMESTAMP NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC')
);

CREATE TABLE getstronger.users
Expand Down
118 changes: 88 additions & 30 deletions go/pkg/orm/auth.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

50 changes: 0 additions & 50 deletions go/pkg/orm/exercises.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 15 additions & 1 deletion go/pkg/repos/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ import (
"context"
"database/sql"
"fmt"
"github.com/crlssn/getstronger/go/pkg/orm"

"github.com/volatiletech/null/v8"
"github.com/volatiletech/sqlboiler/v4/boil"
"golang.org/x/crypto/bcrypt"

"github.com/crlssn/getstronger/go/pkg/orm"
)

type Auth struct {
Expand Down Expand Up @@ -59,3 +62,14 @@ func (a *Auth) CompareEmailAndPassword(ctx context.Context, email, password stri
func (a *Auth) FromEmail(ctx context.Context, email string) (*orm.Auth, error) {
return orm.Auths(orm.AuthWhere.Email.EQ(email)).One(ctx, a.db)
}

func (a *Auth) UpdateRefreshToken(ctx context.Context, authID string, refreshToken string) error {
auth := &orm.Auth{ID: authID, RefreshToken: null.StringFrom(refreshToken)}
_, err := auth.Update(ctx, a.db, boil.Whitelist(orm.AuthColumns.RefreshToken))
return err
}

func (a *Auth) DeleteRefreshToken(ctx context.Context, refreshToken string) error {
_, err := orm.Auths(orm.AuthWhere.RefreshToken.EQ(null.StringFrom(refreshToken))).UpdateAll(ctx, a.db, orm.M{orm.AuthColumns.RefreshToken: nil})
return err
}
22 changes: 19 additions & 3 deletions go/rpc/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ func (h *handler) Login(ctx context.Context, req *connect.Request[v1.LoginReques
return nil, connect.NewError(connect.CodeInternal, errors.New(""))
}

if err = h.repo.UpdateRefreshToken(ctx, auth.ID, refreshToken); err != nil {
log.Error("refresh token upsert failed", zap.Error(err))
return nil, connect.NewError(connect.CodeInternal, errors.New(""))
}

res := connect.NewResponse(&v1.LoginResponse{
AccessToken: accessToken,
})
Expand All @@ -92,7 +97,7 @@ func (h *handler) Login(ctx context.Context, req *connect.Request[v1.LoginReques
HttpOnly: true,
Secure: true,
SameSite: http.SameSiteLaxMode, // TODO: Set to http.SameSiteStrictMode.
Path: apiv1connect.AuthServiceRefreshTokenProcedure,
Path: "/api.v1.AuthService",
MaxAge: int(jwt.ExpiryTimeRefresh),
}
res.Header().Set("Set-Cookie", cookie.String())
Expand Down Expand Up @@ -133,17 +138,28 @@ func (h *handler) RefreshToken(ctx context.Context, _ *connect.Request[v1.Refres
}), nil
}

func (h *handler) Logout(_ context.Context, _ *connect.Request[v1.LogoutRequest]) (*connect.Response[v1.LogoutResponse], error) {
func (h *handler) Logout(ctx context.Context, _ *connect.Request[v1.LogoutRequest]) (*connect.Response[v1.LogoutResponse], error) {
log := h.log.With(xzap.FieldRPC(apiv1connect.AuthServiceLogoutProcedure))

refreshToken, ok := ctx.Value(jwt.ContextKeyRefreshToken).(string)
if !ok {
log.Warn("refresh token not found")
return nil, connect.NewError(connect.CodeUnauthenticated, http.ErrNoCookie)
}

if err := h.repo.DeleteRefreshToken(ctx, refreshToken); err != nil {
log.Error("refresh token deletion failed", zap.Error(err))
return nil, connect.NewError(connect.CodeInternal, errors.New(""))
}

res := connect.NewResponse(&v1.LogoutResponse{})
cookie := &http.Cookie{
Name: "refreshToken",
Value: "",
HttpOnly: true,
Secure: true,
SameSite: http.SameSiteLaxMode, // TODO: Set to http.SameSiteStrictMode.
Path: apiv1connect.AuthServiceRefreshTokenProcedure,
Path: "/api.v1.AuthService",
MaxAge: -1,
}
res.Header().Set("Set-Cookie", cookie.String())
Expand Down
5 changes: 4 additions & 1 deletion js/src/views/Login.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import {LoginRequest} from "@/pb/api/v1/auth_pb";
import {Auth} from "@/clients/clients";
import {ref} from 'vue'
import {RouterLink} from 'vue-router'
import {RouterLink, useRoute} from 'vue-router'
import {ConnectError} from "@connectrpc/connect";
import {useAuthStore} from "@/stores/auth";
import router from "@/router/router";
Expand Down Expand Up @@ -42,6 +42,9 @@ const login = async () => {
</div>

<div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
<div v-if="useRoute().query.success === null" class="bg-green-200 rounded-md py-3 px-5 mb-2 text-sm/6 text-green-800" role="alert">
You have successfully signed up. Please login.
</div>
<div v-if="resError" class="bg-red-200 rounded-md py-3 px-5 mb-2 text-sm/6 text-red-800" role="alert">{{
resError
}}
Expand Down
3 changes: 0 additions & 3 deletions js/src/views/Signup.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,11 @@ const requestError = ref(null);
const router = useRouter()
const signup = async () => {
console.log('signup')
const request = new SignupRequest()
request.email = email.value
request.password = password.value
request.passwordConfirmation = passwordConfirmation.value
console.log('request', request)
try {
requestError.value = null;
await Auth.signup(request);
Expand Down

0 comments on commit ab19e3d

Please sign in to comment.