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: get authenticated user bb-13 #47

Merged
merged 30 commits into from
Aug 22, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
e690859
feat: add AUTHENTICATED_USER route bb-13
MaxKorop Aug 17, 2024
7463563
feat: add type and changed enums bb-13
MaxKorop Aug 18, 2024
8090e01
feat: add jwt secret key environment variable bb-13
MaxKorop Aug 18, 2024
2847eee
feat: change repository and service types and base-controller module …
MaxKorop Aug 18, 2024
3248832
feat: add find logic in user repository and service bb-13
MaxKorop Aug 18, 2024
991fbe3
feat: implement getAuthenticatedUser logic bb-13
MaxKorop Aug 18, 2024
e25a085
fix: code linting problems bb-13
MaxKorop Aug 19, 2024
dbfe98d
fix: code linting problems bb-13
MaxKorop Aug 19, 2024
0ec93b9
Merge branch 'main' of https://github.com/BinaryStudioAcademy/bsa-202…
MaxKorop Aug 19, 2024
c6aec1c
fix: add exceptions bb-13
MaxKorop Aug 20, 2024
91e6c3d
fix: add error message enum bb-13
MaxKorop Aug 20, 2024
5ff595d
refactor: remove unnecessary check bb-13
MaxKorop Aug 20, 2024
13396db
fix: add fastify plugin bb-13
MaxKorop Aug 20, 2024
b998daa
refactor: remove first jwt secret environment variable bb-13
MaxKorop Aug 20, 2024
d860a44
feat: add .prettierignore bb-13
MaxKorop Aug 20, 2024
d530b7b
fix: remove plugin and middleware bb-13
MaxKorop Aug 20, 2024
273d7dd
Merge branch 'main' into 13-feat-get-authenticated-user
MaxKorop Aug 20, 2024
d4187ae
fix: linting and building problems bb-13
MaxKorop Aug 20, 2024
f999d6e
Merge branch 'main' of https://github.com/BinaryStudioAcademy/bsa-202…
MaxKorop Aug 20, 2024
e72c101
fix(backend): fix auth controller bb-13
MaxKorop Aug 21, 2024
d47a0ca
fix(backend): more informative error messages bb-13
MaxKorop Aug 21, 2024
4fa7bba
Merge branch 'main' into 13-feat-get-authenticated-user
MaxKorop Aug 21, 2024
e2507ab
fix(backend/shared): code linting problems bb-13
MaxKorop Aug 21, 2024
116c122
fix(backend/shared): code building problems bb-13
MaxKorop Aug 21, 2024
6f8121e
fix(backend/shared): remove ServerError, change imports/exports for A…
MaxKorop Aug 22, 2024
e3f0f33
fix(backend/shared): remove internal server error message, remove unn…
MaxKorop Aug 22, 2024
bfca4a4
Merge branch 'main' of https://github.com/BinaryStudioAcademy/bsa-202…
MaxKorop Aug 22, 2024
b579c03
fix(backend): change imports/exports bb-13
MaxKorop Aug 22, 2024
1e51f8a
fix(backend): remove headers from APIHandlerOptions bb-13
MaxKorop Aug 22, 2024
b22636e
Merge branch 'main' into 13-feat-get-authenticated-user
fshabanov Aug 22, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
NODE_ENV=development
PORT=3001
HOST=localhost
JWT_SECRET_KEY=someSecretKey

#
# DATABASE
Expand Down
1 change: 1 addition & 0 deletions apps/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"convict": "6.2.4",
"dotenv": "16.4.5",
"fastify": "4.28.1",
"jose": "5.6.3",
"knex": "3.1.0",
"objection": "3.1.4",
"pg": "8.12.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,11 @@ class BaseController implements Controller {
private mapRequest(
request: Parameters<ServerApplicationRouteParameters["handler"]>[0],
): APIHandlerOptions {
const { body, params, query } = request;
const { body, headers, params, query } = request;

return {
body,
headers,
params,
query,
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
type DefaultApiHandlerOptions = {
body?: unknown;
headers?: unknown;
params?: unknown;
query?: unknown;
};
Expand All @@ -8,6 +9,7 @@ type APIHandlerOptions<
T extends DefaultApiHandlerOptions = DefaultApiHandlerOptions,
> = {
body: T["body"];
headers: T["headers"];
params: T["params"];
query: T["query"];
};
Expand Down
2 changes: 1 addition & 1 deletion apps/backend/src/libs/types/repository.type.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
type Repository<T = unknown> = {
create(payload: unknown): Promise<T>;
delete(): Promise<boolean>;
find(): Promise<T>;
find(id: number): Promise<T>;
findAll(): Promise<T[]>;
update(): Promise<T>;
};
Expand Down
2 changes: 1 addition & 1 deletion apps/backend/src/libs/types/service.type.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
type Service<T = unknown> = {
create(payload: unknown): Promise<T>;
delete(): Promise<boolean>;
find(): Promise<T>;
find(id: number): Promise<T>;
findAll(): Promise<{
items: T[];
}>;
Expand Down
40 changes: 40 additions & 0 deletions apps/backend/src/modules/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,46 @@ class AuthController extends BaseController {
body: userSignUpValidationSchema,
},
});

this.addRoute({
handler: (options) =>
this.getAuthenticatedUser(
options as APIHandlerOptions<{
headers: { authorization: string };
}>,
),
method: "GET",
path: AuthApiPath.AUTHENTICATED_USER,
});
}

/**
* @swagger
* /auth/authenticated-user:
* get:
* description: Return authenticated user object
* security:
* - bearerAuth: []
* responses:
* 200:
* description: Successfull operation
* content:
* application/json:
* schema:
* type: object
* $ref: "#/components/schemas/User"
*/
private async getAuthenticatedUser(
options: APIHandlerOptions<{
headers: { authorization: string };
}>,
): Promise<APIHandlerResponse> {
return {
payload: await this.authService.getAuthenticatedUser(
options.headers.authorization,
),
status: HTTPCode.OK,
};
}

/**
Expand Down
67 changes: 67 additions & 0 deletions apps/backend/src/modules/auth/auth.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import * as jose from "jose";
import { createSecretKey } from "node:crypto";
import { HTTPCode, HTTPError } from "shared";

import {
type UserGetOneResponseDto,
type UserSignUpRequestDto,
type UserSignUpResponseDto,
} from "~/modules/users/libs/types/types.js";
Expand All @@ -11,6 +16,68 @@ class AuthService {
this.userService = userService;
}

public async getAuthenticatedUser(
authentication: string,
): Promise<UserGetOneResponseDto> {
try {
if (!authentication) {
throw new HTTPError({
cause: "No authentication token provided",
message: "Unauthorized",
status: HTTPCode.UNATHORIZED,
});
}
const [scheme, token] = authentication.split(" ");
if (scheme !== "Bearer" || !token) {
throw new HTTPError({
cause: "Invalid authentication scheme",
message: "Unauthorized",
status: HTTPCode.UNATHORIZED,
});
}
const secretKey = createSecretKey(
process.env["JWT_SECRET_KEY"] as string,
"utf8",
);
let payload: { id: number } | jose.JWTPayload;
try {
const verifiedToken = await jose.jwtVerify(token, secretKey);
payload = verifiedToken.payload;
} catch {
throw new HTTPError({
cause: "Invalid or expired token",
message: "Unauthorized",
status: HTTPCode.UNATHORIZED,
});
}
const user = await this.userService.find(payload["id"] as number);
if (!user) {
throw new HTTPError({
cause: "Invalid credentials",
message: "Unauthorized",
status: HTTPCode.UNATHORIZED,
});
}
return user;
} catch (error: unknown) {
if (error instanceof HTTPError) {
throw error;
} else if (error instanceof Error) {
throw new HTTPError({
cause: error.message,
message: "Internal Server Error",
status: HTTPCode.INTERNAL_SERVER_ERROR,
});
} else {
throw new HTTPError({
cause: "Unknown error occurred",
message: "Internal Server Error",
status: HTTPCode.INTERNAL_SERVER_ERROR,
});
}
}
}

public signUp(
userRequestDto: UserSignUpRequestDto,
): Promise<UserSignUpResponseDto> {
Expand Down
1 change: 1 addition & 0 deletions apps/backend/src/modules/users/libs/types/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export {
type UserGetAllResponseDto,
type UserGetOneResponseDto,
type UserSignUpRequestDto,
type UserSignUpResponseDto,
} from "shared";
14 changes: 12 additions & 2 deletions apps/backend/src/modules/users/user.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,18 @@ class UserRepository implements Repository {
return Promise.resolve(true);
}

public find(): ReturnType<Repository["find"]> {
return Promise.resolve(null);
public async find(id: number): Promise<null | UserEntity> {
const user = (await this.userModel
.query()
.where({
id,
})
.first()
.execute()) as null | UserModel;
if (!user) {
return null;
}
return UserEntity.initialize(user);
}

public async findAll(): Promise<UserEntity[]> {
Expand Down
11 changes: 9 additions & 2 deletions apps/backend/src/modules/users/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { type UserRepository } from "~/modules/users/user.repository.js";

import {
type UserGetAllResponseDto,
type UserGetOneResponseDto,
type UserSignUpRequestDto,
type UserSignUpResponseDto,
} from "./libs/types/types.js";
Expand Down Expand Up @@ -33,8 +34,14 @@ class UserService implements Service {
return Promise.resolve(true);
}

public find(): ReturnType<Service["find"]> {
return Promise.resolve(null);
public async find(id: number): Promise<null | UserGetOneResponseDto> {
const item = await this.userRepository.find(id);

if (!item) {
return null;
}

return item.toObject();
}

public async findAll(): Promise<UserGetAllResponseDto> {
Expand Down
9 changes: 9 additions & 0 deletions package-lock.json

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

1 change: 1 addition & 0 deletions packages/shared/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export { AuthApiPath } from "./modules/auth/auth.js";
export {
type UserGetAllItemResponseDto,
type UserGetAllResponseDto,
type UserGetOneResponseDto,
UsersApiPath,
type UserSignUpRequestDto,
type UserSignUpResponseDto,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const HTTPCode = {
CREATED: 201,
INTERNAL_SERVER_ERROR: 500,
OK: 200,
UNATHORIZED: 401,
UNPROCESSED_ENTITY: 422,
} as const;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const AuthApiPath = {
AUTHENTICATED_USER: "/authenticated-user",
ROOT: "/",
SIGN_UP: "/sign-up",
} as const;
Expand Down
1 change: 1 addition & 0 deletions packages/shared/src/modules/users/libs/types/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { type UserGetAllItemResponseDto } from "./user-get-all-item-response-dto.type.js";
export { type UserGetAllResponseDto } from "./user-get-all-response-dto.type.js";
export { type UserGetOneResponseDto } from "./user-get-one-response-dto.type.js";
export { type UserSignUpRequestDto } from "./user-sign-up-request-dto.type.js";
export { type UserSignUpResponseDto } from "./user-sign-up-response-dto.type.js";
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
type UserGetOneResponseDto = {
email: string;
id: number;
};

export { type UserGetOneResponseDto };
1 change: 1 addition & 0 deletions packages/shared/src/modules/users/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export { UsersApiPath } from "./libs/enums/enums.js";
export {
type UserGetAllItemResponseDto,
type UserGetAllResponseDto,
type UserGetOneResponseDto,
type UserSignUpRequestDto,
type UserSignUpResponseDto,
} from "./libs/types/types.js";
Expand Down
Loading