Browse Source

feat(core): Store authenticationStrategy on an AuthenticatedSession

Michael Bromley 5 years ago
parent
commit
e737c56ad7

+ 2 - 2
packages/core/src/api/resolvers/admin/auth.resolver.ts

@@ -37,7 +37,7 @@ export class AuthResolver extends BaseAuthResolver {
         @Context('req') req: Request,
         @Context('res') res: Response,
     ): Promise<LoginResult> {
-        return super.login(args, ctx, req, res, 'admin');
+        return super.login(args, ctx, req, res);
     }
 
     @Mutation()
@@ -48,7 +48,7 @@ export class AuthResolver extends BaseAuthResolver {
         @Context('req') req: Request,
         @Context('res') res: Response,
     ): Promise<LoginResult> {
-        return this.createAuthenticatedSession(ctx, args, req, res, 'shop');
+        return this.createAuthenticatedSession(ctx, args, req, res);
     }
 
     @Mutation()

+ 2 - 4
packages/core/src/api/resolvers/base/base-auth.resolver.ts

@@ -38,7 +38,6 @@ export class BaseAuthResolver {
         ctx: RequestContext,
         req: Request,
         res: Response,
-        apiType: ApiType,
     ): Promise<LoginResult> {
         return await this.createAuthenticatedSession(
             ctx,
@@ -47,7 +46,6 @@ export class BaseAuthResolver {
             },
             req,
             res,
-            apiType,
         );
     }
 
@@ -56,7 +54,7 @@ export class BaseAuthResolver {
         if (!token) {
             return false;
         }
-        await this.authService.deleteSessionByToken(ctx, token);
+        await this.authService.destroyAuthenticatedSession(ctx, token);
         setAuthToken({
             req,
             res,
@@ -93,9 +91,9 @@ export class BaseAuthResolver {
         args: MutationAuthenticateArgs,
         req: Request,
         res: Response,
-        apiType: ApiType,
     ) {
         const [method, data] = Object.entries(args.input)[0];
+        const { apiType } = ctx;
         const session = await this.authService.authenticate(ctx, apiType, method, data);
         if (apiType && apiType === 'admin') {
             const administrator = await this.administratorService.findOneByUserId(session.user.id);

+ 2 - 4
packages/core/src/api/resolvers/shop/shop-auth.resolver.ts

@@ -62,7 +62,7 @@ export class ShopAuthResolver extends BaseAuthResolver {
         @Context('res') res: Response,
     ): Promise<LoginResult> {
         this.requireNativeAuthStrategy();
-        return super.login(args, ctx, req, res, 'shop');
+        return super.login(args, ctx, req, res);
     }
 
     @Mutation()
@@ -73,7 +73,7 @@ export class ShopAuthResolver extends BaseAuthResolver {
         @Context('req') req: Request,
         @Context('res') res: Response,
     ): Promise<LoginResult> {
-        return this.createAuthenticatedSession(ctx, args, req, res, 'shop');
+        return this.createAuthenticatedSession(ctx, args, req, res);
     }
 
     @Mutation()
@@ -129,7 +129,6 @@ export class ShopAuthResolver extends BaseAuthResolver {
                 },
                 req,
                 res,
-                'shop',
             );
         } else {
             throw new VerificationTokenError();
@@ -176,7 +175,6 @@ export class ShopAuthResolver extends BaseAuthResolver {
                 },
                 req,
                 res,
-                'shop',
             );
         } else {
             throw new PasswordResetTokenError();

+ 8 - 0
packages/core/src/entity/session/anonymous-session.entity.ts

@@ -5,6 +5,14 @@ import { Order } from '../order/order.entity';
 
 import { Session } from './session.entity';
 
+/**
+ * @description
+ * An anonymous session is created when a unauthenticated user interacts with restricted operations,
+ * such as calling the `activeOrder` query in the Shop API. Anonymous sessions allow a guest Customer
+ * to maintain an order without requiring authentication and a registered account beforehand.
+ *
+ * @docsCategory entities
+ */
 @ChildEntity()
 export class AnonymousSession extends Session {
     constructor(input: DeepPartial<AnonymousSession>) {

+ 18 - 0
packages/core/src/entity/session/authenticated-session.entity.ts

@@ -5,12 +5,30 @@ import { User } from '../user/user.entity';
 
 import { Session } from './session.entity';
 
+/**
+ * @description
+ * An AuthenticatedSession is created upon successful authentication.
+ *
+ * @docsCategory entities
+ */
 @ChildEntity()
 export class AuthenticatedSession extends Session {
     constructor(input: DeepPartial<AuthenticatedSession>) {
         super(input);
     }
 
+    /**
+     * @description
+     * The {@link User} who has authenticated to create this session.
+     */
     @ManyToOne(type => User)
     user: User;
+
+    /**
+     * @description
+     * The name of the {@link AuthenticationStrategy} used when authenticating
+     * to create this session.
+     */
+    @Column()
+    authenticationStrategy: string;
 }

+ 2 - 2
packages/core/src/entity/session/session.entity.ts

@@ -8,8 +8,8 @@ import { User } from '../user/user.entity';
 
 /**
  * @description
- * A Session is created when a user makes a request to the API. A Session can be an AnonymousSession
- * in the case of un-authenticated users, otherwise it is an AuthenticatedSession.
+ * A Session is created when a user makes a request to restricted API operations. A Session can be an {@link AnonymousSession}
+ * in the case of un-authenticated users, otherwise it is an {@link AuthenticatedSession}.
  *
  * @docsCategory entities
  */

+ 13 - 3
packages/core/src/service/services/auth.service.ts

@@ -79,7 +79,7 @@ export class AuthService {
         }
         user.lastLogin = new Date();
         await this.connection.manager.save(user, { reload: false });
-        const session = await this.createNewAuthenticatedSession(ctx, user);
+        const session = await this.createNewAuthenticatedSession(ctx, user, authenticationStrategy);
         const newSession = await this.connection.getRepository(AuthenticatedSession).save(session);
         this.eventBus.publish(new LoginEvent(ctx, user));
         return newSession;
@@ -167,12 +167,20 @@ export class AuthService {
     /**
      * Deletes all sessions for the user associated with the given session token.
      */
-    async deleteSessionByToken(ctx: RequestContext, token: string): Promise<void> {
+    async destroyAuthenticatedSession(ctx: RequestContext, token: string): Promise<void> {
         const session = await this.connection.getRepository(AuthenticatedSession).findOne({
             where: { token },
-            relations: ['user'],
+            relations: ['user', 'user.authenticationMethods'],
         });
+
         if (session) {
+            const authenticationStrategy = this.getAuthenticationStrategy(
+                ctx.apiType,
+                session.authenticationStrategy,
+            );
+            if (typeof authenticationStrategy.onLogOut === 'function') {
+                await authenticationStrategy.onLogOut(session.user);
+            }
             this.eventBus.publish(new LogoutEvent(ctx));
             return this.deleteSessionsByUser(session.user);
         }
@@ -181,6 +189,7 @@ export class AuthService {
     private async createNewAuthenticatedSession(
         ctx: RequestContext,
         user: User,
+        authenticationStrategy: AuthenticationStrategy,
     ): Promise<AuthenticatedSession> {
         const token = await this.generateSessionToken();
         const guestOrder =
@@ -193,6 +202,7 @@ export class AuthService {
             token,
             user,
             activeOrder,
+            authenticationStrategy: authenticationStrategy.name,
             expires: this.getExpiryDate(this.sessionDurationInMs),
             invalidated: false,
         });

+ 3 - 1
packages/core/src/service/services/user.service.ts

@@ -51,7 +51,9 @@ export class UserService {
         user.identifier = identifier;
         const customerRole = await this.roleService.getCustomerRole();
         user.roles = [customerRole];
-        return this.connection.manager.save(this.addNativeAuthenticationMethod(user, identifier, password));
+        return this.connection.manager.save(
+            await this.addNativeAuthenticationMethod(user, identifier, password),
+        );
     }
 
     async addNativeAuthenticationMethod(user: User, identifier: string, password?: string): Promise<User> {