-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: user auth #6
Conversation
}); | ||
} | ||
|
||
export function nullResponse() { | ||
return Type.Null(); | ||
} | ||
|
||
export function setupValidation() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks for info <3
ManagementModule, | ||
JwtModule.register({ | ||
global: true, | ||
secret: process.env.JWT_SECRET, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we register it async to get the env from convig service @mikoscz ?
response: baseResponse(accountSchema), | ||
request: [{ type: "body", schema: createAccountSchema }], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick but could we swap those in order for the easier read? Top down -> request response
request: [{ type: "body", schema: createAccountSchema }], | ||
}) | ||
async register( | ||
data: Static<typeof createAccountSchema>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wdyt if we should also export Static as type from the schema file?
secure: process.env.NODE_ENV === "production", | ||
sameSite: "strict", | ||
maxAge: ACCESS_TOKEN_EXPIRATION_TIME, | ||
}); | ||
|
||
response.cookie("refresh_token", refreshToken, { | ||
httpOnly: true, | ||
secure: process.env.NODE_ENV === "production", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we're using the reverseproxy so we can always use secure I think?
response.cookie("access_token", accessToken, { | ||
httpOnly: true, | ||
secure: process.env.NODE_ENV === "production", | ||
sameSite: "strict", | ||
maxAge: ACCESS_TOKEN_EXPIRATION_TIME, | ||
}); | ||
|
||
response.cookie("refresh_token", newRefreshToken, { | ||
httpOnly: true, | ||
secure: process.env.NODE_ENV === "production", | ||
sameSite: "strict", | ||
maxAge: REFRESH_TOKEN_EXPIRATION_TIME, | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe a method for that in a service since it's in the login and refresh?
} | ||
|
||
@UseGuards(JwtAuthGuard) | ||
@Patch(":id/update") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that can be just a :id so the request would be
PATCH users/:id
async updateUser( | ||
@Param("id") id: string, | ||
@Body() data: UpdateUserBody, | ||
@CurrentUser() currentUser: { userId: string }, | ||
): Promise<BaseResponse<Static<typeof userSchema>>> { | ||
{ | ||
if (currentUser.userId !== id) { | ||
throw new ForbiddenException("You can only update your own account"); | ||
} | ||
|
||
const updatedUser = await this.usersService.updateUser(id, data); | ||
|
||
return new BaseResponse(updatedUser); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
okay it's self update , so maybe we can have another task that'd add some RBAC and allow people with admin privilages to update all users eg. @mikoscz wdyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is out of the scope of this one. But definitely, we want something like that.
try { | ||
const allUsers = await this.db | ||
.select(omit(getTableColumns(users), ["password", "refreshToken"])) | ||
.from(users); | ||
|
||
return allUsers; | ||
} catch (error) { | ||
throw new InternalServerErrorException("Error fetching users"); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in array gets you can leave the try catchI believe - it'd return empty array if nothing is found and throw 500 either way if anything goes bad
const [user] = await this.db | ||
.select(omit(getTableColumns(users), ["password", "refreshToken"])) | ||
.from(users) | ||
.where(eq(users.id, id)); | ||
|
||
if (!user) { | ||
throw new NotFoundException("User not found"); | ||
} | ||
|
||
return user; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here I think try catch is not necessary for the case here
const token = request.cookies["access_token"]; | ||
|
||
if (!token) { | ||
throw new UnauthorizedException("Access token not found"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cool, just to leave it up there . Remember then to use Unauthorized to actions that should log out user on the frontend also, and If used doesn't have access to something like a resource - to return ForbiddenException
Remove the |
We should do 180 on the auth, instead of marking every endpoint with guard we want to guard all endpoints and whitelist only the public. |
response: baseResponse(userSchema), | ||
}) | ||
async getUserById( | ||
@Param() params: { id: string }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that with nestjs-typebox @Param
decorator is unnecessary, params are passed in the same order as validate params(they are missing)
@Public() | ||
@UseGuards(AuthGuard("local")) | ||
@Post("login") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mikoscz is this global jwt auth guard properly implemented and @Public
can it be used together with @UseGuards(AuthGuard("local"))
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Write some tests and we will see :D
const [user] = await this.db | ||
.select() | ||
.from(users) | ||
.where(eq(users.email, email)); | ||
|
||
if (!user) return null; | ||
|
||
const [userCredentials] = await this.db | ||
.select() | ||
.from(credentials) | ||
.where(eq(credentials.userId, user.id)); | ||
|
||
if (!userCredentials) return null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use join
@@ -0,0 +1,3 @@ | |||
import { SetMetadata } from "@nestjs/common"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move it to common both guards and decorators
name: text("name").notNull(), | ||
description: text("description"), | ||
userId: uuid("user_id") | ||
.references(() => users.id) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add cascade
|
||
public async deleteUser(id: string) { | ||
const deleted = await this.db.transaction(async (trx) => { | ||
await trx.delete(credentials).where(eq(credentials.userId, id)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Set the onDelete cascade on the db level and then you can remove that
await this.updateRefreshToken(userId, null); | ||
} | ||
|
||
public async refreshTokens(userId: string, refreshToken: string) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you explain me how we would like to use refresh token? I am thinking why do we need to store it in the db(probably I am missing something)
async updateUser( | ||
id: string, | ||
@Body() data: UpdateUserBody, | ||
@CurrentUser() currentUser: { userId: string }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should unify user type and create common type for it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would go to something similar to user currentUser.id
since currentUser.userId
says the same info twice, just a nitpick
@@ -23,3 +29,7 @@ export function baseResponse(data: TObject) { | |||
export function nullResponse() { | |||
return Type.Null(); | |||
} | |||
|
|||
export function setupValidation() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one looks like util since we are using it only during the bootstrap. Probably we need more formats eg. email
I saw it is used but IDK if it is registered.
maxAge: ACCESS_TOKEN_EXPIRATION_TIME, | ||
}); | ||
|
||
response.cookie("refresh_token", refreshToken, { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the refresh I would limit the path to only endpoint that handles refresh.
|
||
try { | ||
const payload = await this.jwtService.verifyAsync(token, { | ||
secret: process.env.JWT_SECRET, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use configService?
|
||
try { | ||
const payload = await this.jwtService.verifyAsync(refreshToken, { | ||
secret: process.env.REFRESH_SECRET, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use configService?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GJ overall, we are still missing few pieces:
- tests
- support for Bearer(for mobile apps)
- idk if we need email validation?
For the email validation I'd say we need it, but maybe we can add it as separate task that will handle Email integration and update the auth flow with email confirmation? |
@mikoscz I've added the Bearer support - what you think? |
* feat: configure jest for TypeScript and test database setup * feat: expand e2e tests for auth.controller and add new migration file * feat: update password change functionality and schema * feat: initialize test context and services * feat: create unit test setup function * feat: initialize test context and services for e2e tests * feat: create e2e test setup function * refactor: improve user and credential factories, add userWithCredentialFactory * feat: update dependencies and add JWT_REFRESH_SECRET * refactor: refactor user factory and add hashPassword function * fix: update password handling in user controller e2e tests * feat: refactor userFactory and authService.register * chore: update assertions in auth and users service tests
No description provided.