Browse Source

refactor: Overhaul authN, remove JWT, use sessions table

Relates to #25.
Michael Bromley 7 years ago
parent
commit
4848279f26
34 changed files with 291 additions and 433 deletions
  1. 3 2
      admin-ui/src/app/core/components/app-shell/app-shell.component.ts
  2. 21 16
      admin-ui/src/app/core/providers/auth/auth.service.ts
  3. 2 2
      admin-ui/src/app/data/client-state/client-types.graphql
  4. 3 11
      admin-ui/src/app/data/data.module.ts
  5. 8 3
      admin-ui/src/app/data/definitions/auth-definitions.ts
  6. 8 3
      admin-ui/src/app/data/providers/auth-data.service.ts
  7. 7 3
      admin-ui/src/app/data/providers/client-data.service.ts
  8. 1 0
      admin-ui/src/app/data/providers/data.service.mock.ts
  9. 1 0
      admin-ui/src/app/data/providers/fetch-adapter.ts
  10. 8 18
      admin-ui/src/app/data/providers/interceptor.ts
  11. 4 1
      admin-ui/src/app/login/components/login/login.component.html
  12. 2 1
      admin-ui/src/app/login/components/login/login.component.ts
  13. 21 4
      admin-ui/yarn.lock
  14. 0 0
      schema.json
  15. 1 1
      server/dev-config.ts
  16. 1 1
      server/e2e/config/test-config.ts
  17. 8 10
      server/package.json
  18. 0 5
      server/src/api/api.module.ts
  19. 13 83
      server/src/api/common/auth-guard.ts
  20. 2 0
      server/src/api/common/graphql-config.service.ts
  21. 7 0
      server/src/api/common/id-codec.spec.ts
  22. 2 1
      server/src/api/common/id-codec.ts
  23. 0 39
      server/src/api/common/token-interceptor.ts
  24. 24 17
      server/src/api/resolvers/auth.resolver.ts
  25. 2 2
      server/src/api/types/auth.api.graphql
  26. 10 0
      server/src/app.module.ts
  27. 4 5
      server/src/config/default-config.ts
  28. 8 13
      server/src/config/vendure-config.ts
  29. 2 0
      server/src/entity/entities.ts
  30. 23 0
      server/src/entity/session/session.entity.ts
  31. 44 58
      server/src/service/providers/auth.service.ts
  32. 37 114
      server/yarn.lock
  33. 14 19
      shared/generated-types.ts
  34. 0 1
      shared/shared-constants.ts

+ 3 - 2
admin-ui/src/app/core/components/app-shell/app-shell.component.ts

@@ -23,7 +23,8 @@ export class AppShellComponent implements OnInit {
     }
 
     logOut() {
-        this.authService.logOut();
-        this.router.navigate(['/login']);
+        this.authService.logOut().subscribe(() => {
+            this.router.navigate(['/login']);
+        });
     }
 }

+ 21 - 16
admin-ui/src/app/core/providers/auth/auth.service.ts

@@ -1,9 +1,8 @@
 import { Injectable } from '@angular/core';
 import { Observable, of } from 'rxjs';
-import { catchError, map, mergeMap, switchMap } from 'rxjs/operators';
-import { AttemptLogin, SetAsLoggedIn } from 'shared/generated-types';
+import { catchError, mapTo, mergeMap, switchMap } from 'rxjs/operators';
+import { SetAsLoggedIn } from 'shared/generated-types';
 
-import { _ } from '../../../core/providers/i18n/mark-for-extraction';
 import { DataService } from '../../../data/providers/data.service';
 import { LocalStorageService } from '../local-storage/local-storage.service';
 
@@ -18,8 +17,8 @@ export class AuthService {
      * Attempts to log in via the REST login endpoint and updates the app
      * state on success.
      */
-    logIn(username: string, password: string): Observable<SetAsLoggedIn> {
-        return this.dataService.auth.attemptLogin(username, password).pipe(
+    logIn(username: string, password: string, rememberMe: boolean): Observable<SetAsLoggedIn> {
+        return this.dataService.auth.attemptLogin(username, password, rememberMe).pipe(
             switchMap(response => {
                 this.localStorageService.setForSession(
                     'activeChannelToken',
@@ -33,9 +32,19 @@ export class AuthService {
     /**
      * Update the user status to being logged out.
      */
-    logOut(): void {
-        this.dataService.client.logOut();
-        this.localStorageService.remove('authToken');
+    logOut(): Observable<boolean> {
+        return this.dataService.client.userStatus().single$.pipe(
+            switchMap(status => {
+                if (status.userStatus.isLoggedIn) {
+                    return this.dataService.client
+                        .logOut()
+                        .pipe(mergeMap(() => this.dataService.auth.logOut()));
+                } else {
+                    return [];
+                }
+            }),
+            mapTo(true),
+        );
     }
 
     /**
@@ -59,18 +68,14 @@ export class AuthService {
      * that token against the API.
      */
     validateAuthToken(): Observable<boolean> {
-        if (!this.localStorageService.get('authToken')) {
-            return of(false);
-        }
-
         return this.dataService.auth.checkLoggedIn().single$.pipe(
-            map(result => {
+            mergeMap(result => {
                 if (!result.me) {
-                    return false;
+                    return of(false);
                 }
-                this.dataService.client.loginSuccess(result.me.identifier);
-                return true;
+                return this.dataService.client.loginSuccess(result.me.identifier);
             }),
+            mapTo(true),
             catchError(err => of(false)),
         );
     }

+ 2 - 2
admin-ui/src/app/data/client-state/client-types.graphql

@@ -7,8 +7,8 @@ extend type Query {
 extend type Mutation {
     requestStarted: Int!
     requestCompleted: Int!
-    setAsLoggedIn(username: String!, loginTime: String!): UserStatus
-    setAsLoggedOut: UserStatus
+    setAsLoggedIn(username: String!, loginTime: String!): UserStatus!
+    setAsLoggedOut: UserStatus!
     setUiLanguage(languageCode: LanguageCode): LanguageCode
 }
 

+ 3 - 11
admin-ui/src/app/data/data.module.ts

@@ -7,7 +7,7 @@ import { ApolloLink } from 'apollo-link';
 import { setContext } from 'apollo-link-context';
 import { withClientState } from 'apollo-link-state';
 import { createUploadLink } from 'apollo-upload-client';
-import { API_PATH, REFRESH_TOKEN_KEY } from 'shared/shared-constants';
+import { API_PATH } from 'shared/shared-constants';
 
 import { environment } from '../../environments/environment';
 import { API_URL } from '../app.config';
@@ -44,20 +44,12 @@ export function createApollo(
             stateLink,
             new OmitTypenameLink(),
             setContext(() => {
-                // Add JWT auth token & channel token to all requests.
                 const channelToken = localStorageService.get('activeChannelToken');
-                const authToken = localStorageService.get('authToken') || '';
-                const refreshToken = localStorageService.get('refreshToken') || '';
-                if (!authToken) {
-                    return {};
-                } else {
+                if (channelToken) {
                     return {
-                        // prettier-ignore
                         headers: {
-                            'Authorization': `Bearer ${authToken}`,
                             'vendure-token': channelToken,
-                            [REFRESH_TOKEN_KEY]: refreshToken,
-                        }
+                        },
                     };
                 }
             }),

+ 8 - 3
admin-ui/src/app/data/definitions/auth-definitions.ts

@@ -5,13 +5,12 @@ export const CURRENT_USER_FRAGMENT = gql`
         id
         identifier
         channelTokens
-        roles
     }
 `;
 
 export const ATTEMPT_LOGIN = gql`
-    mutation AttemptLogin($username: String!, $password: String!) {
-        login(username: $username, password: $password) {
+    mutation AttemptLogin($username: String!, $password: String!, $rememberMe: Boolean!) {
+        login(username: $username, password: $password, rememberMe: $rememberMe) {
             user {
                 ...CurrentUser
             }
@@ -20,6 +19,12 @@ export const ATTEMPT_LOGIN = gql`
     ${CURRENT_USER_FRAGMENT}
 `;
 
+export const LOG_OUT = gql`
+    mutation LogOut {
+        logout
+    }
+`;
+
 export const GET_CURRENT_USER = gql`
     query GetCurrentUser {
         me {

+ 8 - 3
admin-ui/src/app/data/providers/auth-data.service.ts

@@ -1,7 +1,7 @@
 import { Observable } from 'rxjs';
-import { AttemptLogin, AttemptLoginVariables, GetCurrentUser } from 'shared/generated-types';
+import { AttemptLogin, AttemptLoginVariables, GetCurrentUser, LogOut } from 'shared/generated-types';
 
-import { ATTEMPT_LOGIN, GET_CURRENT_USER } from '../definitions/auth-definitions';
+import { ATTEMPT_LOGIN, GET_CURRENT_USER, LOG_OUT } from '../definitions/auth-definitions';
 import { QueryResult } from '../query-result';
 
 import { BaseDataService } from './base-data.service';
@@ -13,10 +13,15 @@ export class AuthDataService {
         return this.baseDataService.query<GetCurrentUser>(GET_CURRENT_USER);
     }
 
-    attemptLogin(username: string, password: string): Observable<AttemptLogin> {
+    attemptLogin(username: string, password: string, rememberMe: boolean): Observable<AttemptLogin> {
         return this.baseDataService.mutate<AttemptLogin, AttemptLoginVariables>(ATTEMPT_LOGIN, {
             username,
             password,
+            rememberMe,
         });
     }
+
+    logOut(): Observable<LogOut> {
+        return this.baseDataService.mutate<LogOut>(LOG_OUT);
+    }
 }

+ 7 - 3
admin-ui/src/app/data/providers/client-data.service.ts

@@ -27,6 +27,10 @@ import { QueryResult } from '../query-result';
 
 import { BaseDataService } from './base-data.service';
 
+/**
+ * Note: local queries all have a fetch-policy of "cache-first" explicitly specified due to:
+ * https://github.com/apollographql/apollo-link-state/issues/236
+ */
 export class ClientDataService {
     constructor(private baseDataService: BaseDataService) {}
 
@@ -39,7 +43,7 @@ export class ClientDataService {
     }
 
     getNetworkStatus(): QueryResult<GetNetworkStatus> {
-        return this.baseDataService.query<GetNetworkStatus>(GET_NEWTORK_STATUS);
+        return this.baseDataService.query<GetNetworkStatus>(GET_NEWTORK_STATUS, {}, 'cache-first');
     }
 
     loginSuccess(username: string): Observable<SetAsLoggedIn> {
@@ -54,11 +58,11 @@ export class ClientDataService {
     }
 
     userStatus(): QueryResult<GetUserStatus> {
-        return this.baseDataService.query<GetUserStatus>(GET_USER_STATUS);
+        return this.baseDataService.query<GetUserStatus>(GET_USER_STATUS, {}, 'cache-first');
     }
 
     uiState(): QueryResult<GetUiState> {
-        return this.baseDataService.query<GetUiState>(GET_UI_STATE);
+        return this.baseDataService.query<GetUiState>(GET_UI_STATE, {}, 'cache-first');
     }
 
     setUiLanguage(languageCode: LanguageCode): Observable<SetUiLanguage> {

+ 1 - 0
admin-ui/src/app/data/providers/data.service.mock.ts

@@ -68,6 +68,7 @@ export class MockDataService implements DataServiceMock {
     auth = {
         checkLoggedIn: spyObservable('checkLoggedIn'),
         attemptLogin: spyObservable('attemptLogin'),
+        logOut: spyObservable('logOut'),
     };
     facet = {
         getFacets: spyQueryResult('getFacets'),

+ 1 - 0
admin-ui/src/app/data/providers/fetch-adapter.ts

@@ -20,6 +20,7 @@ export class FetchAdapter {
                 headers: init.headers as any,
                 observe: 'response',
                 responseType: 'json',
+                withCredentials: true,
             })
             .toPromise()
             .then(result => {

+ 8 - 18
admin-ui/src/app/data/providers/interceptor.ts

@@ -10,7 +10,6 @@ import { Injectable, Injector } from '@angular/core';
 import { Router } from '@angular/router';
 import { Observable } from 'rxjs';
 import { tap } from 'rxjs/operators';
-import { AUTH_TOKEN_KEY, REFRESH_TOKEN_KEY } from 'shared/shared-constants';
 
 import { API_URL } from '../../app.config';
 import { AuthService } from '../../core/providers/auth/auth.service';
@@ -42,18 +41,6 @@ export class DefaultInterceptor implements HttpInterceptor {
             tap(
                 event => {
                     if (event instanceof HttpResponse) {
-                        if (event.headers.get(AUTH_TOKEN_KEY)) {
-                            this.localStorageService.setForSession(
-                                'authToken',
-                                event.headers.get(AUTH_TOKEN_KEY),
-                            );
-                        }
-                        if (event.headers.get(REFRESH_TOKEN_KEY)) {
-                            this.localStorageService.set(
-                                'refreshToken',
-                                event.headers.get(REFRESH_TOKEN_KEY),
-                            );
-                        }
                         this.notifyOnError(event);
                         this.dataService.client.completeRequest().subscribe();
                     }
@@ -62,6 +49,8 @@ export class DefaultInterceptor implements HttpInterceptor {
                     if (err instanceof HttpErrorResponse) {
                         this.notifyOnError(err);
                         this.dataService.client.completeRequest().subscribe();
+                    } else {
+                        this.displayErrorNotification(err.message);
                     }
                 },
             ),
@@ -87,11 +76,12 @@ export class DefaultInterceptor implements HttpInterceptor {
                         break;
                     case 403:
                         this.displayErrorNotification(_(`error.403-forbidden`));
-                        this.authService.logOut();
-                        this.router.navigate(['/login'], {
-                            queryParams: {
-                                [AUTH_REDIRECT_PARAM]: btoa(this.router.url),
-                            },
+                        this.authService.logOut().subscribe(() => {
+                            this.router.navigate(['/login'], {
+                                queryParams: {
+                                    [AUTH_REDIRECT_PARAM]: btoa(this.router.url),
+                                },
+                            });
                         });
                         break;
                     default:

+ 4 - 1
admin-ui/src/app/login/components/login/login.component.html

@@ -17,7 +17,10 @@
                    [(ngModel)]="password"
                    [placeholder]="'common.password' | translate">
             <div class="checkbox">
-                <input type="checkbox" id="rememberme">
+                <input type="checkbox"
+                       id="rememberme"
+                       name="rememberme"
+                       [(ngModel)]="rememberMe">
                 <label for="rememberme">
                     {{ 'common.remember-me' | translate }}
                 </label>

+ 2 - 1
admin-ui/src/app/login/components/login/login.component.ts

@@ -12,11 +12,12 @@ import { AUTH_REDIRECT_PARAM } from '../../../data/providers/interceptor';
 export class LoginComponent {
     username = '';
     password = '';
+    rememberMe = false;
 
     constructor(private authService: AuthService, private router: Router) {}
 
     logIn(): void {
-        this.authService.logIn(this.username, this.password).subscribe(
+        this.authService.logIn(this.username, this.password, this.rememberMe).subscribe(
             () => {
                 const redirect = this.getRedirectRoute();
                 this.router.navigate([redirect ? redirect : '/']);

+ 21 - 4
admin-ui/yarn.lock

@@ -901,8 +901,8 @@ apollo-link-http@^1.5.4:
     apollo-link-http-common "^0.2.4"
 
 apollo-link-state@^0.4.1:
-  version "0.4.1"
-  resolved "https://registry.yarnpkg.com/apollo-link-state/-/apollo-link-state-0.4.1.tgz#65e9e0e12c67936b8c4b12b8438434f393104579"
+  version "0.4.2"
+  resolved "https://registry.yarnpkg.com/apollo-link-state/-/apollo-link-state-0.4.2.tgz#ac00e9be9b0ca89eae0be6ba31fe904b80bbe2e8"
   dependencies:
     apollo-utilities "^1.0.8"
     graphql-anywhere "^4.1.0-alpha.0"
@@ -923,12 +923,19 @@ apollo-upload-client@^8.1.0:
     apollo-link-http-common "^0.2.4"
     extract-files "^3.1.0"
 
-apollo-utilities@^1.0.0, apollo-utilities@^1.0.1, apollo-utilities@^1.0.19, apollo-utilities@^1.0.8:
+apollo-utilities@^1.0.0, apollo-utilities@^1.0.1:
   version "1.0.19"
   resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.0.19.tgz#f2d253bb8aa1395b62ded2f7749884233b5838e2"
   dependencies:
     fast-json-stable-stringify "^2.0.0"
 
+apollo-utilities@^1.0.19, apollo-utilities@^1.0.21, apollo-utilities@^1.0.8:
+  version "1.0.21"
+  resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.0.21.tgz#cb8b5779fe275850b16046ff8373f4af2de90765"
+  dependencies:
+    fast-json-stable-stringify "^2.0.0"
+    fclone "^1.0.11"
+
 apollo@1.7.1:
   version "1.7.1"
   resolved "https://registry.yarnpkg.com/apollo/-/apollo-1.7.1.tgz#29ae6095f6a55fce18f45b0b9b77898dd03523f2"
@@ -2971,6 +2978,10 @@ fbjs@^0.8.16:
     setimmediate "^1.0.5"
     ua-parser-js "^0.7.18"
 
+fclone@^1.0.11:
+  version "1.0.11"
+  resolved "https://registry.yarnpkg.com/fclone/-/fclone-1.0.11.tgz#10e85da38bfea7fc599341c296ee1d77266ee640"
+
 fd-slicer@~1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.0.1.tgz#8b5bcbd9ec327c5041bf9ab023fd6750f1177e65"
@@ -3394,7 +3405,13 @@ graceful-fs@4.1.11, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6:
   version "4.1.11"
   resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
 
-graphql-anywhere@^4.1.0-alpha.0, graphql-anywhere@^4.1.17:
+graphql-anywhere@^4.1.0-alpha.0:
+  version "4.1.19"
+  resolved "https://registry.yarnpkg.com/graphql-anywhere/-/graphql-anywhere-4.1.19.tgz#5f6ca3b58218e5449f4798e3c6d942fcd2fef082"
+  dependencies:
+    apollo-utilities "^1.0.21"
+
+graphql-anywhere@^4.1.17:
   version "4.1.17"
   resolved "https://registry.yarnpkg.com/graphql-anywhere/-/graphql-anywhere-4.1.17.tgz#5c9128d0c5c1623dc46ecbdf0916845061f1965a"
   dependencies:

File diff suppressed because it is too large
+ 0 - 0
schema.json


+ 1 - 1
server/dev-config.ts

@@ -10,7 +10,7 @@ import { DefaultAssetServerPlugin } from './src/plugin/default-asset-server/defa
 export const devConfig: VendureConfig = {
     authOptions: {
         disableAuth: false,
-        jwtSecret: 'some-secret',
+        sessionSecret: 'some-secret',
     },
     port: API_PORT,
     apiPath: API_PATH,

+ 1 - 1
server/e2e/config/test-config.ts

@@ -16,7 +16,7 @@ export const testConfig: VendureConfig = {
     apiPath: API_PATH,
     cors: true,
     authOptions: {
-        jwtSecret: 'some-secret',
+        sessionSecret: 'some-secret',
     },
     dbConnectionOptions: {
         type: 'sqljs',

+ 8 - 10
server/package.json

@@ -20,15 +20,15 @@
     "dist/**/*"
   ],
   "dependencies": {
-    "@nestjs/common": "^5.3.2",
-    "@nestjs/core": "^5.3.2",
-    "@nestjs/graphql": "5.1.2",
-    "@nestjs/passport": "^5.0.1",
-    "@nestjs/testing": "^5.3.1",
-    "@nestjs/typeorm": "^5.2.0",
+    "@nestjs/common": "5.3.6",
+    "@nestjs/core": "5.3.6",
+    "@nestjs/graphql": "5.4.0",
+    "@nestjs/testing": "5.3.8",
+    "@nestjs/typeorm": "^5.2.2",
     "apollo-server-express": "^2.0.4",
     "bcrypt": "^3.0.0",
     "body-parser": "^1.18.3",
+    "cookie-session": "^2.0.0-beta.3",
     "express": "^4.16.3",
     "fs-extra": "^7.0.0",
     "graphql": "^14.0.0",
@@ -41,10 +41,8 @@
     "i18next-express-middleware": "^1.3.2",
     "i18next-icu": "^0.4.0",
     "i18next-node-fs-backend": "^2.0.0",
-    "jsonwebtoken": "^8.2.2",
+    "ms": "^2.1.1",
     "mysql": "^2.16.0",
-    "passport": "^0.4.0",
-    "passport-jwt": "^4.0.0",
     "reflect-metadata": "^0.1.12",
     "rxjs": "^6.2.0",
     "sharp": "^0.20.8",
@@ -53,13 +51,13 @@
   },
   "devDependencies": {
     "@types/bcrypt": "^2.0.0",
+    "@types/cookie-session": "^2.0.36",
     "@types/express": "^4.0.39",
     "@types/faker": "^4.1.3",
     "@types/fs-extra": "^5.0.4",
     "@types/i18next": "^8.4.3",
     "@types/i18next-express-middleware": "^0.0.33",
     "@types/jest": "^23.3.1",
-    "@types/jsonwebtoken": "^7.2.7",
     "@types/node": "^9.3.0",
     "@types/sharp": "^0.17.10",
     "faker": "^4.1.0",

+ 0 - 5
server/src/api/api.module.ts

@@ -12,7 +12,6 @@ import { GraphqlConfigService } from './common/graphql-config.service';
 import { IdInterceptor } from './common/id-interceptor';
 import { RequestContextService } from './common/request-context.service';
 import { RolesGuard } from './common/roles-guard';
-import { TokenInterceptor } from './common/token-interceptor';
 import { AdministratorResolver } from './resolvers/administrator.resolver';
 import { AssetResolver } from './resolvers/asset.resolver';
 import { AuthResolver } from './resolvers/auth.resolver';
@@ -61,10 +60,6 @@ const exportedProviders = [
             provide: APP_GUARD,
             useClass: RolesGuard,
         },
-        {
-            provide: APP_INTERCEPTOR,
-            useClass: TokenInterceptor,
-        },
         {
             provide: APP_INTERCEPTOR,
             useClass: AssetInterceptor,

+ 13 - 83
server/src/api/common/auth-guard.ts

@@ -1,19 +1,15 @@
-import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
+import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
 import { Reflector } from '@nestjs/core';
 import { GqlExecutionContext } from '@nestjs/graphql';
-import { Request, Response } from 'express';
-import { TokenExpiredError } from 'jsonwebtoken';
-import { ExtractJwt, Strategy } from 'passport-jwt';
-import { AUTH_TOKEN_KEY, REFRESH_TOKEN_KEY } from 'shared/shared-constants';
 
-import { JwtPayload } from '../../common/types/auth-types';
 import { ConfigService } from '../../config/config.service';
 import { AuthService } from '../../service/providers/auth.service';
 
 import { PERMISSIONS_METADATA_KEY } from './roles-guard';
 
 /**
- * A guard which uses passport.js & the passport-jwt strategy to authenticate incoming GraphQL requests.
+ * A guard which checks for the existence of a valid session token in the request and if found,
+ * attaches the current User entity to the request.
  */
 @Injectable()
 export class AuthGuard implements CanActivate {
@@ -23,19 +19,7 @@ export class AuthGuard implements CanActivate {
         private reflector: Reflector,
         private configService: ConfigService,
         private authService: AuthService,
-    ) {
-        this.strategy = new Strategy(
-            {
-                secretOrKey: configService.authOptions.jwtSecret,
-                algorithms: ['HS256'],
-                jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
-                jsonWebTokenOptions: {
-                    expiresIn: this.configService.authOptions.expiresIn,
-                },
-            },
-            (payload: any, done: () => void) => this.validate(payload, done),
-        );
-    }
+    ) {}
 
     async canActivate(context: ExecutionContext): Promise<boolean> {
         const permissions = this.reflector.get<string[]>(PERMISSIONS_METADATA_KEY, context.getHandler());
@@ -43,70 +27,16 @@ export class AuthGuard implements CanActivate {
             return true;
         }
         const ctx = GqlExecutionContext.create(context).getContext();
-        return this.authenticate(ctx.req, ctx.res);
-    }
-
-    async validate(payload: JwtPayload, done: (err: Error | null, user: any) => void) {
-        const user = await this.authService.validateUser(payload.identifier);
-        if (!user) {
-            return done(new UnauthorizedException(), false);
+        const session = ctx.req.session;
+        if (!session || !session.token) {
+            return false;
         }
-        done(null, user);
-    }
-
-    /**
-     * Wraps the JwtStrategy.authenticate() call in a Promise, and also patches
-     * the methods which it expects to exist because it is designed to run as
-     * an Express middleware function.
-     */
-    private authenticate(request: Request, response: Response): Promise<boolean> {
-        return new Promise((resolve, reject) => {
-            this.strategy.fail = async info => {
-                if (info instanceof TokenExpiredError) {
-                    const currentAuthToken = this.extractAuthToken(request);
-                    const currentRefreshToken = request.get(REFRESH_TOKEN_KEY);
-                    if (currentAuthToken && currentRefreshToken) {
-                        try {
-                            const result = await this.authService.refreshTokens(
-                                currentAuthToken,
-                                currentRefreshToken,
-                            );
-                            if (result) {
-                                // set the new token headers
-                                response.set(AUTH_TOKEN_KEY, result.authToken);
-                                response.set(REFRESH_TOKEN_KEY, result.refreshToken);
-                                (request as any).user = result.user;
-                                resolve(true);
-                                return;
-                            }
-                        } catch (e) {
-                            // fall through
-                        }
-                    }
-                }
-                resolve(false);
-            };
-            this.strategy.success = (user, info) => {
-                (request as any).user = user;
-                resolve(true);
-            };
-            this.strategy.error = err => {
-                reject(err);
-            };
-            this.strategy.authenticate(request);
-        });
-    }
-
-    /**
-     * Extract the token from the 'Authorization: Bearer <token>' header
-     */
-    private extractAuthToken(req: Request): string | undefined {
-        const authHeader = req.get('authorization');
-        if (authHeader) {
-            const matches = authHeader.match(/bearer\s+(.+)/i);
-            if (matches && matches[1]) {
-                return matches[1];
-            }
+        const activeSession = await this.authService.validateSession(session.token);
+        if (activeSession) {
+            ctx.req.user = activeSession.user;
+            return true;
+        } else {
+            return false;
         }
     }
 }

+ 2 - 0
server/src/api/common/graphql-config.service.ts

@@ -50,6 +50,8 @@ export class GraphqlConfigService implements GqlOptionsFactory {
             formatError: err => {
                 return this.i18nService.translateError(err);
             },
+            // This is handled by the Express cors plugin
+            cors: false,
         };
     }
 

+ 7 - 0
server/src/api/common/id-codec.spec.ts

@@ -24,6 +24,13 @@ describe('IdCodecService', () => {
             expect(result).toEqual(ENCODED);
         });
 
+        it('works with a boolean', () => {
+            const input = true;
+
+            const result = idCodec.encode(input);
+            expect(result).toEqual(true);
+        });
+
         it('passes through null or undefined without throwing', () => {
             expect(idCodec.encode(null as any)).toBeNull();
             expect(idCodec.encode(undefined as any)).toBeUndefined();

+ 2 - 1
server/src/api/common/id-codec.ts

@@ -38,7 +38,7 @@ export class IdCodec {
      * then the default recursive behaviour will be used.
      * @return An encoded clone of the target
      */
-    encode<T extends string | number | object | undefined>(target: T, transformKeys?: string[]): T {
+    encode<T extends string | number | boolean | object | undefined>(target: T, transformKeys?: string[]): T {
         const transformKeysWithId = [...(transformKeys || []), 'id'];
         return this.transformRecursive(
             target,
@@ -51,6 +51,7 @@ export class IdCodec {
         // noinspection SuspiciousInstanceOfGuard
         if (
             target == null ||
+            typeof target === 'boolean' ||
             target instanceof Promise ||
             target instanceof Date ||
             target instanceof RegExp

+ 0 - 39
server/src/api/common/token-interceptor.ts

@@ -1,39 +0,0 @@
-import { ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
-import { GqlExecutionContext } from '@nestjs/graphql';
-import { Response } from 'express';
-import { Observable } from 'rxjs';
-import { map } from 'rxjs/operators';
-import { AUTH_TOKEN_KEY, REFRESH_TOKEN_KEY } from 'shared/shared-constants';
-
-import { ConfigService } from '../../config/config.service';
-
-/**
- * Transfers auth tokens from response body to headers.
- */
-@Injectable()
-export class TokenInterceptor implements NestInterceptor {
-    constructor(private configService: ConfigService) {}
-
-    /**
-     * If a resolver returns an object with keys matching the token keys, these values
-     * are removed from the response and transferred to the header.
-     */
-    intercept(context: ExecutionContext, call$: Observable<any>): Observable<any> {
-        return call$.pipe(
-            map(data => {
-                const ctx = GqlExecutionContext.create(context).getContext();
-                this.transferKeyToResponseHeader(AUTH_TOKEN_KEY, data, ctx.res);
-                this.transferKeyToResponseHeader(REFRESH_TOKEN_KEY, data, ctx.res);
-                return data;
-            }),
-        );
-    }
-
-    private transferKeyToResponseHeader(key: string, data: any | null, response: Response) {
-        if (data && data.hasOwnProperty(key)) {
-            const authToken = data[key];
-            delete data[key];
-            response.set(key, authToken);
-        }
-    }
-}

+ 24 - 17
server/src/api/resolvers/auth.resolver.ts

@@ -1,8 +1,8 @@
 import { Args, Context, Mutation, Query, Resolver } from '@nestjs/graphql';
 import { Request } from 'express';
-import { Permission } from 'shared/generated-types';
+import * as ms from 'ms';
+import { AttemptLoginVariables, Permission } from 'shared/generated-types';
 
-import { AUTH_TOKEN_KEY, REFRESH_TOKEN_KEY } from '../../../../shared/shared-constants';
 import { User } from '../../entity/user/user.entity';
 import { AuthService } from '../../service/providers/auth.service';
 import { ChannelService } from '../../service/providers/channel.service';
@@ -14,31 +14,42 @@ export class AuthResolver {
 
     /**
      * 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.
+     * the user data and sets the session.
      */
     @Mutation()
-    async login(@Args() args: { username: string; password: string }) {
-        const { user, authToken, refreshToken } = await this.authService.createTokens(
-            args.username,
-            args.password,
-        );
+    async login(@Args() args: AttemptLoginVariables, @Context('req') request: Request) {
+        const session = await this.authService.authenticate(args.username, args.password);
 
-        if (authToken) {
+        if (session) {
+            if (request.session) {
+                if (args.rememberMe) {
+                    request.sessionOptions.maxAge = ms('1y');
+                }
+                request.session.token = session.token;
+            }
             return {
-                [AUTH_TOKEN_KEY]: authToken,
-                [REFRESH_TOKEN_KEY]: refreshToken,
-                user: this.publiclyAccessibleUser(user),
+                user: this.publiclyAccessibleUser(session.user),
             };
         }
     }
 
+    @Mutation()
+    @Allow(Permission.Authenticated)
+    async logout(@Context('req') request: Request & { user: User }) {
+        await this.authService.invalidateUserSessions(request.user);
+        if (request.session) {
+            request.session = undefined;
+        }
+        return true;
+    }
+
     /**
      * Returns information about the current authenticated user.
      */
     @Query()
     @Allow(Permission.Authenticated)
     async me(@Context('req') request: Request & { user: User }) {
-        const user = await this.authService.validateUser(request.user.identifier);
+        const user = await this.authService.getUserById(request.user.id);
         return user ? this.publiclyAccessibleUser(user) : null;
     }
 
@@ -49,10 +60,6 @@ export class AuthResolver {
         return {
             id: user.id,
             identifier: user.identifier,
-            roles: user.roles.reduce(
-                (roleTypes, role) => [...roleTypes, ...role.permissions],
-                [] as Permission[],
-            ),
             channelTokens: this.getAvailableChannelTokens(user),
         };
     }

+ 2 - 2
server/src/api/types/auth.api.graphql

@@ -3,7 +3,8 @@ type Query {
 }
 
 type Mutation {
-    login(username: String!, password: String!): LoginResult!
+    login(username: String!, password: String!, rememberMe: Boolean!): LoginResult!
+    logout: Boolean!
 }
 
 type LoginResult {
@@ -13,6 +14,5 @@ type LoginResult {
 type CurrentUser {
     id: ID!
     identifier: String!
-    roles: [String!]!
     channelTokens: [String!]!
 }

+ 10 - 0
server/src/app.module.ts

@@ -1,6 +1,8 @@
 import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
+import cookieSession = require('cookie-session');
 import { RequestHandler } from 'express';
 import { GraphQLDateTime } from 'graphql-iso-date';
+import * as ms from 'ms';
 
 import { ApiModule } from './api/api.module';
 import { ConfigModule } from './config/config.module';
@@ -20,6 +22,14 @@ export class AppModule implements NestModule {
 
         const defaultMiddleware: Array<{ handler: RequestHandler; route?: string }> = [
             { handler: this.i18nService.handle(), route: this.configService.apiPath },
+            {
+                handler: cookieSession({
+                    name: 'session',
+                    secret: this.configService.authOptions.sessionSecret,
+                    httpOnly: true,
+                }),
+                route: this.configService.apiPath,
+            },
         ];
         const allMiddleware = defaultMiddleware.concat(this.configService.middleware);
         const middlewareByRoute = this.groupMiddlewareByRoute(allMiddleware);

+ 4 - 5
server/src/config/default-config.ts

@@ -1,5 +1,5 @@
 import { LanguageCode } from 'shared/generated-types';
-import { API_PATH, API_PORT, AUTH_TOKEN_KEY, REFRESH_TOKEN_KEY } from 'shared/shared-constants';
+import { API_PATH, API_PORT } from 'shared/shared-constants';
 import { CustomFields } from 'shared/shared-types';
 
 import { ReadOnlyRequired } from '../common/types/common-types';
@@ -19,13 +19,12 @@ export const defaultConfig: ReadOnlyRequired<VendureConfig> = {
     port: API_PORT,
     cors: {
         origin: true,
-        exposedHeaders: [AUTH_TOKEN_KEY, REFRESH_TOKEN_KEY],
+        credentials: true,
     },
     authOptions: {
         disableAuth: false,
-        jwtSecret: 'jwt-secret',
-        expiresIn: '5m',
-        refreshEvery: '7d',
+        sessionSecret: 'jwt-secret',
+        sessionDuration: '7d',
     },
     apiPath: API_PATH,
     entityIdStrategy: new AutoIncrementIdStrategy(),

+ 8 - 13
server/src/config/vendure-config.ts

@@ -22,27 +22,22 @@ export interface AuthOptions {
      */
     disableAuth?: boolean;
     /**
-     * The secret used for signing each JWT used in authenticating users.
+     * The secret used for signing the session cookies for authenticated users.
+     *
      * In production applications, this should not be stored as a string in
      * source control for security reasons, but may be loaded from an external
      * file not under source control, or from an environment variable, for example.
      * See https://stackoverflow.com/a/30090120/772859
      */
-    jwtSecret: string;
+    sessionSecret: string;
     /**
-     * Auth token duration. Typically this should be short-lived (on the order of minutes) to allow the
-     * revocation of tokens.
-     * Expressed in seconds or a string describing a time span
+     * Session duration, i.e. the time which must elapse from the last authenticted request
+     * after which the user must re-authenticate.
+     *
+     * Expressed as a string describing a time span
      * [zeit/ms](https://github.com/zeit/ms.js).  Eg: 60, "2 days", "10h", "7d"
      */
-    expiresIn?: string | number;
-    /**
-     * Refresh token duration. This shoud be on the order of days or weeks, depending on how long a user
-     * should be able to remain logged in without having to re-authenticate.
-     * Expressed in seconds or a string describing a time span
-     * [zeit/ms](https://github.com/zeit/ms.js).  Eg: 60, "2 days", "10h", "7d"
-     */
-    refreshEvery?: string | number;
+    sessionDuration?: string | number;
 }
 
 export interface VendureConfig {

+ 2 - 0
server/src/entity/entities.ts

@@ -17,6 +17,7 @@ import { ProductVariant } from './product-variant/product-variant.entity';
 import { ProductTranslation } from './product/product-translation.entity';
 import { Product } from './product/product.entity';
 import { Role } from './role/role.entity';
+import { Session } from './session/session.entity';
 import { User } from './user/user.entity';
 
 /**
@@ -42,5 +43,6 @@ export const coreEntitiesMap = {
     ProductVariantPrice,
     ProductVariantTranslation,
     Role,
+    Session,
     User,
 };

+ 23 - 0
server/src/entity/session/session.entity.ts

@@ -0,0 +1,23 @@
+import { DeepPartial } from 'shared/shared-types';
+import { Column, Entity, Index, ManyToOne } from 'typeorm';
+
+import { VendureEntity } from '../base/base.entity';
+import { User } from '../user/user.entity';
+
+@Entity()
+export class Session extends VendureEntity {
+    constructor(input: DeepPartial<Session>) {
+        super(input);
+    }
+
+    @Index({ unique: true })
+    @Column()
+    token: string;
+
+    @ManyToOne(type => User)
+    user: User;
+
+    @Column() expires: Date;
+
+    @Column() invalidated: boolean;
+}

+ 44 - 58
server/src/service/providers/auth.service.ts

@@ -1,11 +1,12 @@
 import { Injectable, UnauthorizedException } from '@nestjs/common';
 import { InjectConnection } from '@nestjs/typeorm';
-import * as jwt from 'jsonwebtoken';
-import { Permission } from 'shared/generated-types';
+import * as crypto from 'crypto';
+import * as ms from 'ms';
+import { ID } from 'shared/shared-types';
 import { Connection } from 'typeorm';
 
-import { JwtPayload } from '../../common/types/auth-types';
 import { ConfigService } from '../../config/config.service';
+import { Session } from '../../entity/session/session.entity';
 import { User } from '../../entity/user/user.entity';
 
 import { PasswordService } from './password.service';
@@ -19,75 +20,50 @@ export class AuthService {
     ) {}
 
     /**
-     * Creates auth & refresh tokens after user login.
+     * Authenticates a user's credentials and if okay, creates a new session.
      */
-    async createTokens(
-        identifier: string,
-        password: string,
-    ): Promise<{ user: User; authToken: string; refreshToken: string }> {
+    async authenticate(identifier: string, password: string): Promise<Session> {
         const user = await this.getUserFromIdentifier(identifier);
         const passwordMatches = await this.passwordService.check(password, user.passwordHash);
         if (!passwordMatches) {
             throw new UnauthorizedException();
         }
-        const { authToken, refreshToken } = this.createTokensForUser(user);
-        return { user, authToken, refreshToken };
+        const token = await this.generateSessionToken();
+        const session = new Session({
+            token,
+            user,
+            expires: new Date(Date.now() + ms(this.configService.authOptions.sessionDuration)),
+        });
+        await this.invalidateUserSessions(user);
+        // save the new session
+        const newSession = this.connection.getRepository(Session).save(session);
+        return newSession;
     }
 
     /**
-     * Attempts to refresh the authToken based on the provided refreshToken.
+     * Looks for a valid session with the given token and returns one if found.
      */
-    async refreshTokens(
-        token: string,
-        refreshToken: string,
-    ): Promise<{ user: User; authToken: string; refreshToken: string } | null> {
-        const jwtPayload = jwt.decode(token) as JwtPayload | null;
-
-        if (jwtPayload) {
-            const user = await this.getUserFromIdentifier(jwtPayload.identifier);
-
-            try {
-                jwt.verify(refreshToken, this.getRefreshTokenSecret(user));
-            } catch (e) {
-                throw new UnauthorizedException();
-            }
-
-            const newTokens = this.createTokensForUser(user);
-            return {
-                user,
-                authToken: newTokens.authToken,
-                refreshToken: newTokens.refreshToken,
-            };
-        } else {
-            return null;
+    async validateSession(token: string): Promise<Session | undefined> {
+        const session = await this.connection.getRepository(Session).findOne({
+            where: { token, invalidated: false },
+            relations: ['user', 'user.roles', 'user.roles.channels'],
+        });
+        if (session && session.expires > new Date()) {
+            return session;
         }
     }
 
-    async validateUser(identifier: string): Promise<User | undefined> {
-        return await this.connection.getRepository(User).findOne({
-            where: { identifier },
-            relations: ['roles', 'roles.channels'],
-        });
+    /**
+     * Invalidates all existing sessions for the given user.
+     */
+    async invalidateUserSessions(user: User): Promise<void> {
+        await this.connection.getRepository(Session).update({ user }, { invalidated: true });
     }
 
-    private createTokensForUser(user: User): { authToken: string; refreshToken: string } {
-        const payload: JwtPayload = {
-            identifier: user.identifier,
-            roles: user.roles.reduce((roles, r) => [...roles, ...r.permissions], [] as Permission[]),
-        };
-        const authToken = jwt.sign(payload, this.configService.authOptions.jwtSecret, {
-            expiresIn: this.configService.authOptions.expiresIn,
-            algorithm: 'HS256',
-        });
-
-        // The refreshToken is signed with a combination of the JWT secret and the user's password hash.
-        // This means that changing the password will invalidate any active refresh tokens automatically.
-        const refreshToken = jwt.sign({ identifier: user.identifier }, this.getRefreshTokenSecret(user), {
-            expiresIn: this.configService.authOptions.refreshEvery,
-            algorithm: 'HS256',
+    async getUserById(userId: ID): Promise<User | undefined> {
+        return this.connection.getRepository(User).findOne(userId, {
+            relations: ['roles', 'roles.channels'],
         });
-
-        return { authToken, refreshToken };
     }
 
     private async getUserFromIdentifier(identifier: string): Promise<User> {
@@ -101,7 +77,17 @@ export class AuthService {
         return user;
     }
 
-    private getRefreshTokenSecret(user: User): string {
-        return this.configService.authOptions.jwtSecret + '_' + user.passwordHash;
+    /**
+     * Generates a random session token.
+     */
+    private generateSessionToken(): Promise<string> {
+        return new Promise((resolve, reject) => {
+            crypto.randomBytes(32, (err, buf) => {
+                if (err) {
+                    reject(err);
+                }
+                resolve(buf.toString('hex'));
+            });
+        });
     }
 }

+ 37 - 114
server/yarn.lock

@@ -49,9 +49,9 @@
     call-me-maybe "^1.0.1"
     glob-to-regexp "^0.3.0"
 
-"@nestjs/common@^5.3.2":
-  version "5.3.2"
-  resolved "https://registry.yarnpkg.com/@nestjs/common/-/common-5.3.2.tgz#53b872d1e9e0e7058194c6da04c2102a66c22b39"
+"@nestjs/common@5.3.6":
+  version "5.3.6"
+  resolved "https://registry.yarnpkg.com/@nestjs/common/-/common-5.3.6.tgz#9d91505f47a6d46f5f436a5fa4604c595e4e2c3b"
   dependencies:
     axios "0.17.1"
     cli-color "1.2.0"
@@ -59,9 +59,9 @@
     multer "1.3.0"
     uuid "3.3.2"
 
-"@nestjs/core@^5.3.2":
-  version "5.3.2"
-  resolved "https://registry.yarnpkg.com/@nestjs/core/-/core-5.3.2.tgz#abe3d8a322500d59b3fb7c1fa24ace713aa5e729"
+"@nestjs/core@5.3.6":
+  version "5.3.6"
+  resolved "https://registry.yarnpkg.com/@nestjs/core/-/core-5.3.6.tgz#60a74ce341679b553303d119f6126d1fc3878cfc"
   dependencies:
     "@nuxtjs/opencollective" "0.1.0"
     body-parser "1.18.3"
@@ -73,30 +73,29 @@
     path-to-regexp "2.2.1"
     uuid "3.3.2"
 
-"@nestjs/graphql@5.1.2":
-  version "5.1.2"
-  resolved "https://registry.yarnpkg.com/@nestjs/graphql/-/graphql-5.1.2.tgz#66472be935daf31a6828ef86aa8dba1b7bfbbc73"
+"@nestjs/graphql@5.4.0":
+  version "5.4.0"
+  resolved "https://registry.yarnpkg.com/@nestjs/graphql/-/graphql-5.4.0.tgz#18e96afdb264b5a3447550d855161ece1ec92d8f"
   dependencies:
     glob "^7.1.2"
     graphql-tools "^3.1.1"
     lodash "^4.17.4"
     merge-graphql-schemas "^1.3.0"
     ts-simple-ast "^14.4.2"
+    uuid "3.3.2"
 
-"@nestjs/passport@^5.0.1":
-  version "5.0.1"
-  resolved "https://registry.yarnpkg.com/@nestjs/passport/-/passport-5.0.1.tgz#49abadb0390f172cd1c6fea3f52497fd1941b347"
-
-"@nestjs/testing@^5.3.1":
-  version "5.3.1"
-  resolved "https://registry.yarnpkg.com/@nestjs/testing/-/testing-5.3.1.tgz#4c3e5a301dbda5eb157575da0f6fd2597a021508"
+"@nestjs/testing@5.3.8":
+  version "5.3.8"
+  resolved "https://registry.yarnpkg.com/@nestjs/testing/-/testing-5.3.8.tgz#b99d453e1a0b0a6d3439442347900e39d13d138d"
   dependencies:
     deprecate "1.0.0"
     optional "0.1.4"
 
-"@nestjs/typeorm@^5.2.0":
-  version "5.2.0"
-  resolved "https://registry.yarnpkg.com/@nestjs/typeorm/-/typeorm-5.2.0.tgz#315be2a0eafca611f53e82d926295d0be880abf8"
+"@nestjs/typeorm@^5.2.2":
+  version "5.2.2"
+  resolved "https://registry.yarnpkg.com/@nestjs/typeorm/-/typeorm-5.2.2.tgz#f32e508b498e4ac0d0b0f78af5844da413e0a713"
+  dependencies:
+    uuid "3.3.2"
 
 "@nodelib/fs.stat@^1.0.1":
   version "1.1.2"
@@ -177,6 +176,12 @@
   dependencies:
     "@types/node" "*"
 
+"@types/cookie-session@^2.0.36":
+  version "2.0.36"
+  resolved "https://registry.yarnpkg.com/@types/cookie-session/-/cookie-session-2.0.36.tgz#404cd47a0fef64107c04fc07be6aef13a05073ab"
+  dependencies:
+    "@types/express" "*"
+
 "@types/cors@^2.8.4":
   version "2.8.4"
   resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.4.tgz#50991a759a29c0b89492751008c6af7a7c8267b0"
@@ -236,12 +241,6 @@
   version "0.0.29"
   resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
 
-"@types/jsonwebtoken@^7.2.7":
-  version "7.2.8"
-  resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-7.2.8.tgz#8d199dab4ddb5bba3234f8311b804d2027af2b3a"
-  dependencies:
-    "@types/node" "*"
-
 "@types/long@^4.0.0":
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.0.tgz#719551d2352d301ac8b81db732acb6bdc28dbdef"
@@ -1027,10 +1026,6 @@ buffer-alloc@^1.1.0:
     buffer-alloc-unsafe "^1.1.0"
     buffer-fill "^1.0.0"
 
-buffer-equal-constant-time@1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
-
 buffer-equal@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe"
@@ -1389,6 +1384,15 @@ convert-source-map@^1.4.0, convert-source-map@^1.5.0, convert-source-map@^1.5.1:
   version "1.5.1"
   resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5"
 
+cookie-session@^2.0.0-beta.3:
+  version "2.0.0-beta.3"
+  resolved "https://registry.yarnpkg.com/cookie-session/-/cookie-session-2.0.0-beta.3.tgz#4e446bd9f85bd7e27d3e226f4e99af12011a4386"
+  dependencies:
+    cookies "0.7.1"
+    debug "3.1.0"
+    on-headers "~1.0.1"
+    safe-buffer "5.1.1"
+
 cookie-signature@1.0.6:
   version "1.0.6"
   resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
@@ -1675,12 +1679,6 @@ ecc-jsbn@~0.1.1:
   dependencies:
     jsbn "~0.1.0"
 
-ecdsa-sig-formatter@1.0.10:
-  version "1.0.10"
-  resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz#1c595000f04a8897dfb85000892a0f4c33af86c3"
-  dependencies:
-    safe-buffer "^5.0.1"
-
 ee-first@1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
@@ -3503,20 +3501,6 @@ jsonify@~0.0.0:
   version "0.0.0"
   resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
 
-jsonwebtoken@^8.2.0, jsonwebtoken@^8.2.2:
-  version "8.3.0"
-  resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.3.0.tgz#056c90eee9a65ed6e6c72ddb0a1d325109aaf643"
-  dependencies:
-    jws "^3.1.5"
-    lodash.includes "^4.3.0"
-    lodash.isboolean "^3.0.3"
-    lodash.isinteger "^4.0.4"
-    lodash.isnumber "^3.0.3"
-    lodash.isplainobject "^4.0.6"
-    lodash.isstring "^4.0.1"
-    lodash.once "^4.0.0"
-    ms "^2.1.1"
-
 jsprim@^1.2.2:
   version "1.4.1"
   resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
@@ -3530,21 +3514,6 @@ just-debounce@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/just-debounce/-/just-debounce-1.0.0.tgz#87fccfaeffc0b68cd19d55f6722943f929ea35ea"
 
-jwa@^1.1.5:
-  version "1.1.6"
-  resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.1.6.tgz#87240e76c9808dbde18783cf2264ef4929ee50e6"
-  dependencies:
-    buffer-equal-constant-time "1.0.1"
-    ecdsa-sig-formatter "1.0.10"
-    safe-buffer "^5.0.1"
-
-jws@^3.1.5:
-  version "3.1.5"
-  resolved "https://registry.yarnpkg.com/jws/-/jws-3.1.5.tgz#80d12d05b293d1e841e7cb8b4e69e561adcf834f"
-  dependencies:
-    jwa "^1.1.5"
-    safe-buffer "^5.0.1"
-
 keygrip@~1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/keygrip/-/keygrip-1.0.2.tgz#ad3297c557069dea8bcfe7a4fa491b75c5ddeb91"
@@ -3653,34 +3622,6 @@ lodash.debounce@^4.0.8:
   version "4.0.8"
   resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
 
-lodash.includes@^4.3.0:
-  version "4.3.0"
-  resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
-
-lodash.isboolean@^3.0.3:
-  version "3.0.3"
-  resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"
-
-lodash.isinteger@^4.0.4:
-  version "4.0.4"
-  resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343"
-
-lodash.isnumber@^3.0.3:
-  version "3.0.3"
-  resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc"
-
-lodash.isplainobject@^4.0.6:
-  version "4.0.6"
-  resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
-
-lodash.isstring@^4.0.1:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
-
-lodash.once@^4.0.0:
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
-
 lodash.sortby@^4.7.0:
   version "4.7.0"
   resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
@@ -4322,6 +4263,10 @@ on-finished@^2.3.0, on-finished@~2.3.0:
   dependencies:
     ee-first "1.1.1"
 
+on-headers@~1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.1.tgz#928f5d0f470d49342651ea6794b0857c100693f7"
+
 once@^1.3.0, once@^1.3.1, once@^1.3.2, once@^1.4.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
@@ -4463,24 +4408,6 @@ pascalcase@^0.1.1:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14"
 
-passport-jwt@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/passport-jwt/-/passport-jwt-4.0.0.tgz#7f0be7ba942e28b9f5d22c2ebbb8ce96ef7cf065"
-  dependencies:
-    jsonwebtoken "^8.2.0"
-    passport-strategy "^1.0.0"
-
-passport-strategy@1.x.x, passport-strategy@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4"
-
-passport@^0.4.0:
-  version "0.4.0"
-  resolved "https://registry.yarnpkg.com/passport/-/passport-0.4.0.tgz#c5095691347bd5ad3b5e180238c3914d16f05811"
-  dependencies:
-    passport-strategy "1.x.x"
-    pause "0.0.1"
-
 path-dirname@^1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0"
@@ -4549,10 +4476,6 @@ pause-stream@0.0.11:
   dependencies:
     through "~2.3"
 
-pause@0.0.1:
-  version "0.0.1"
-  resolved "https://registry.yarnpkg.com/pause/-/pause-0.0.1.tgz#1d408b3fdb76923b9543d96fb4c9dfd535d9cb5d"
-
 performance-now@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"

+ 14 - 19
shared/generated-types.ts

@@ -362,7 +362,6 @@ export interface AttemptLogin_login_user {
   id: string;
   identifier: string;
   channelTokens: string[];
-  roles: string[];
 }
 
 export interface AttemptLogin_login {
@@ -377,6 +376,18 @@ export interface AttemptLogin {
 export interface AttemptLoginVariables {
   username: string;
   password: string;
+  rememberMe: boolean;
+}
+
+/* tslint:disable */
+// This file was automatically generated and should not be edited.
+
+// ====================================================
+// GraphQL mutation operation: LogOut
+// ====================================================
+
+export interface LogOut {
+  logout: boolean;
 }
 
 /* tslint:disable */
@@ -391,7 +402,6 @@ export interface GetCurrentUser_me {
   id: string;
   identifier: string;
   channelTokens: string[];
-  roles: string[];
 }
 
 export interface GetCurrentUser {
@@ -717,15 +727,8 @@ export interface RequestCompleted {
 // GraphQL mutation operation: SetAsLoggedIn
 // ====================================================
 
-export interface SetAsLoggedIn_setAsLoggedIn {
-  __typename: "UserStatus";
-  username: string;
-  isLoggedIn: boolean;
-  loginTime: string;
-}
-
 export interface SetAsLoggedIn {
-  setAsLoggedIn: SetAsLoggedIn_setAsLoggedIn | null;
+  setAsLoggedIn: boolean;
 }
 
 export interface SetAsLoggedInVariables {
@@ -740,15 +743,8 @@ export interface SetAsLoggedInVariables {
 // GraphQL mutation operation: SetAsLoggedOut
 // ====================================================
 
-export interface SetAsLoggedOut_setAsLoggedOut {
-  __typename: "UserStatus";
-  username: string;
-  isLoggedIn: boolean;
-  loginTime: string;
-}
-
 export interface SetAsLoggedOut {
-  setAsLoggedOut: SetAsLoggedOut_setAsLoggedOut | null;
+  setAsLoggedOut: boolean;
 }
 
 /* tslint:disable */
@@ -1652,7 +1648,6 @@ export interface CurrentUser {
   id: string;
   identifier: string;
   channelTokens: string[];
-  roles: string[];
 }
 
 /* tslint:disable */

+ 0 - 1
shared/shared-constants.ts

@@ -5,7 +5,6 @@
 export const API_PORT = 3000;
 export const API_PATH = 'api';
 export const AUTH_TOKEN_KEY = 'vendure-auth-token';
-export const REFRESH_TOKEN_KEY = 'vendure-refresh-token';
 export const DEFAULT_CHANNEL_CODE = '__default_channel__';
 export const SUPER_ADMIN_ROLE_CODE = '__super_admin_role__';
 export const SUPER_ADMIN_ROLE_DESCRIPTION = 'SuperAdmin';

Some files were not shown because too many files changed in this diff