Jelajahi Sumber

Return user data from login endpoint, add `me` endpoint.

Michael Bromley 7 tahun lalu
induk
melakukan
394e8d0132

+ 33 - 3
modules/core/api/auth/auth.controller.ts

@@ -1,19 +1,49 @@
-import { Body, Controller, Post } from "@nestjs/common";
+import { Body, Controller, Get, Post, Req } from "@nestjs/common";
 import { LoginDto } from "./login.dto";
 import { AuthService } from "../../auth/auth.service";
+import { RolesGuard } from "../../auth/roles-guard";
+import { Role } from "../../auth/role";
+import { UserEntity } from "../../entity/user/user.entity";
 
 @Controller('auth')
 export class AuthController {
 
     constructor(private authService: AuthService) {}
 
+    /**
+     * Attempts a login given the username and password of a user. If successful, returns
+     * the user data and a token to be used by Bearer auth.
+     */
     @Post('login')
     async login(@Body() loginDto: LoginDto) {
-        const token = await this.authService.createToken(loginDto.username, loginDto.password);
+        const { user, token } = await this.authService.createToken(loginDto.username, loginDto.password);
+
         if (token) {
             return {
-                token
+                token,
+                user: this.publiclyAccessibleUser(user)
             };
         }
     }
+
+    /**
+     * Returns information about the current authenticated user.
+     */
+    @RolesGuard([Role.Authenticated])
+    @Get('me')
+    async me(@Req() request) {
+        const user = request.user as UserEntity;
+        return this.publiclyAccessibleUser(user);
+    }
+
+    /**
+     * Exposes a subset of the UserEntity properties which we want to expose to the public API.
+     */
+    private publiclyAccessibleUser(user: UserEntity): Pick<UserEntity, 'id' | 'identifier' | 'roles'> {
+        return {
+            id: user.id,
+            identifier: user.identifier,
+            roles: user.roles
+        };
+    }
 }

+ 0 - 2
modules/core/api/customer/customer.resolver.ts

@@ -3,8 +3,6 @@ import { CustomerService } from './customer.service';
 import { Address } from '../../entity/address/address.interface';
 import { CustomerEntity } from "../../entity/customer/customer.entity";
 import { Customer } from "../../entity/customer/customer.interface";
-import { RolesGuard } from "../../auth/roles-guard";
-import { Role } from "../../auth/role";
 
 @Resolver('Customer')
 export class CustomerResolver {

+ 5 - 2
modules/core/auth/auth.service.ts

@@ -7,6 +7,7 @@ import { Connection } from "typeorm";
 import { InjectConnection } from "@nestjs/typeorm";
 import { UserEntity } from "../entity/user/user.entity";
 
+// TODO: make this configurable e.g. from environment
 export const JWT_SECRET = 'some_secret';
 
 @Injectable()
@@ -15,7 +16,7 @@ export class AuthService {
     constructor(private passwordService: PasswordService,
                 @InjectConnection() private connection: Connection) {}
 
-    async createToken(identifier: string, password: string): Promise<string> {
+    async createToken(identifier: string, password: string): Promise<{ user: UserEntity; token: string; }> {
         const user = await this.connection.getRepository(UserEntity)
             .findOne({
                 where: {
@@ -33,7 +34,9 @@ export class AuthService {
             throw new UnauthorizedException();
         }
         const payload: JwtPayload = { identifier , roles: user.roles };
-        return jwt.sign(payload, JWT_SECRET, { expiresIn: 3600 });
+        const token = jwt.sign(payload, JWT_SECRET, { expiresIn: 3600 });
+
+        return { user, token };
     }
 
     async validateUser(payload: JwtPayload): Promise<any> {

+ 2 - 0
modules/core/auth/role.ts

@@ -2,6 +2,8 @@
  * All possible authorization roles for registered users.
  */
 export enum Role {
+    // The Authenticated role means simply that the user is logged in
+    Authenticated = 'Authenticated',
     Customer = 'Customer',
     Superadmin = 'Superadmin'
 }

+ 11 - 1
modules/core/auth/roles-guard.ts

@@ -18,7 +18,17 @@ import { ExtractJwt, Strategy } from 'passport-jwt';
  * ```
  */
 export function RolesGuard(roles: Role[]) {
-    return UseGuards(AuthGuard('jwt'), forRoles(roles));
+    const guards: CanActivate[] = [AuthGuard('jwt')];
+
+    if (roles.length && !authenticatedOnly(roles)) {
+        guards.push(forRoles(roles));
+    }
+
+    return UseGuards(...guards);
+}
+
+function authenticatedOnly(roles: Role[]): boolean {
+    return roles.length === 1 && roles[0] === Role.Authenticated;
 }
 
 /**