Skip to content
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

Merged
merged 19 commits into from
Jul 18, 2024
Merged

feat: user auth #6

merged 19 commits into from
Jul 18, 2024

Conversation

typeWolffo
Copy link
Member

No description provided.

});
}

export function nullResponse() {
return Type.Null();
}

export function setupValidation() {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

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,
Copy link
Member

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 ?

Comment on lines 47 to 48
response: baseResponse(accountSchema),
request: [{ type: "body", schema: createAccountSchema }],
Copy link
Member

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

Comment on lines 34 to 37
request: [{ type: "body", schema: createAccountSchema }],
})
async register(
data: Static<typeof createAccountSchema>,
Copy link
Member

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?

Comment on lines 59 to 66
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",
Copy link
Member

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?

Comment on lines 111 to 123
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,
});
Copy link
Member

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")
Copy link
Member

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

Comment on lines 71 to 85
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);
}
}
Copy link
Member

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?

Copy link
Member

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.

Comment on lines 18 to 26
try {
const allUsers = await this.db
.select(omit(getTableColumns(users), ["password", "refreshToken"]))
.from(users);

return allUsers;
} catch (error) {
throw new InternalServerErrorException("Error fetching users");
}
Copy link
Member

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

Comment on lines 31 to 40
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;
Copy link
Member

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");
Copy link
Member

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

@mikoscz
Copy link
Member

mikoscz commented Jul 10, 2024

Remove the trys and use error filtering instead.

@mikoscz
Copy link
Member

mikoscz commented Jul 10, 2024

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 },
Copy link
Member

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)

Comment on lines +54 to +56
@Public()
@UseGuards(AuthGuard("local"))
@Post("login")
Copy link
Member Author

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"))?

Copy link
Member

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

@typeWolffo typeWolffo requested review from mikoscz and k1eu July 11, 2024 00:21
Comment on lines 100 to 112
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;
Copy link
Member

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";
Copy link
Member

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)
Copy link
Member

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));
Copy link
Member

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) {
Copy link
Member

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 },
Copy link
Member

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

Copy link
Member

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() {
Copy link
Member

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, {
Copy link
Member

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,
Copy link
Member

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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use configService?

Copy link
Member

@mikoscz mikoscz left a 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?

@k1eu
Copy link
Member

k1eu commented Jul 11, 2024

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?

@typeWolffo
Copy link
Member Author

  • support for Bearer(for mobile apps)

@mikoscz I've added the Bearer support - what you think?

@typeWolffo typeWolffo requested a review from mikoscz July 11, 2024 20:53
* 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
@typeWolffo typeWolffo merged commit 6dbc6d0 into main Jul 18, 2024
@typeWolffo typeWolffo deleted the jw/user-auth branch July 18, 2024 12:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants