Browse Source

feat(core): Allow cookie & bearer session tokens at the same time

Closes #960
Michael Bromley 4 years ago
parent
commit
fc6b890937

+ 4 - 1
packages/admin-ui-plugin/src/plugin.ts

@@ -212,7 +212,10 @@ export class AdminUiPlugin implements NestModule {
             adminApiPath: propOrDefault('adminApiPath', this.configService.apiOptions.adminApiPath),
             adminApiPath: propOrDefault('adminApiPath', this.configService.apiOptions.adminApiPath),
             apiHost: propOrDefault('apiHost', 'auto'),
             apiHost: propOrDefault('apiHost', 'auto'),
             apiPort: propOrDefault('apiPort', 'auto'),
             apiPort: propOrDefault('apiPort', 'auto'),
-            tokenMethod: propOrDefault('tokenMethod', authOptions.tokenMethod || 'cookie'),
+            tokenMethod: propOrDefault(
+                'tokenMethod',
+                authOptions.tokenMethod === 'bearer' ? 'bearer' : 'cookie',
+            ),
             authTokenHeaderKey: propOrDefault(
             authTokenHeaderKey: propOrDefault(
                 'authTokenHeaderKey',
                 'authTokenHeaderKey',
                 authOptions.authTokenHeaderKey || DEFAULT_AUTH_TOKEN_HEADER_KEY,
                 authOptions.authTokenHeaderKey || DEFAULT_AUTH_TOKEN_HEADER_KEY,

+ 28 - 11
packages/core/src/api/common/extract-session-token.ts

@@ -8,19 +8,36 @@ import { AuthOptions } from '../../config/vendure-config';
  */
  */
 export function extractSessionToken(
 export function extractSessionToken(
     req: Request,
     req: Request,
-    tokenMethod: AuthOptions['tokenMethod'],
+    tokenMethod: Exclude<AuthOptions['tokenMethod'], undefined>,
 ): string | undefined {
 ): string | undefined {
+    const tokenFromCookie = getFromCookie(req);
+    const tokenFromHeader = getFromHeader(req);
+
     if (tokenMethod === 'cookie') {
     if (tokenMethod === 'cookie') {
-        if (req.session && req.session.token) {
-            return req.session.token;
-        }
-    } else {
-        const authHeader = req.get('Authorization');
-        if (authHeader) {
-            const matches = authHeader.match(/bearer\s+(.+)$/i);
-            if (matches) {
-                return matches[1];
-            }
+        return tokenFromCookie;
+    } else if (tokenMethod === 'bearer') {
+        return tokenFromHeader;
+    }
+    if (tokenMethod.includes('cookie') && tokenFromCookie) {
+        return tokenFromCookie;
+    }
+    if (tokenMethod.includes('bearer') && tokenFromHeader) {
+        return tokenFromHeader;
+    }
+}
+
+function getFromCookie(req: Request): string | undefined {
+    if (req.session && req.session.token) {
+        return req.session.token;
+    }
+}
+
+function getFromHeader(req: Request): string | undefined {
+    const authHeader = req.get('Authorization');
+    if (authHeader) {
+        const matches = authHeader.match(/bearer\s+(.+)$/i);
+        if (matches) {
+            return matches[1];
         }
         }
     }
     }
 }
 }

+ 10 - 2
packages/core/src/api/common/set-session-token.ts

@@ -15,14 +15,22 @@ export function setSessionToken(options: {
     res: Response;
     res: Response;
 }) {
 }) {
     const { sessionToken, rememberMe, authOptions, req, res } = options;
     const { sessionToken, rememberMe, authOptions, req, res } = options;
-    if (authOptions.tokenMethod === 'cookie') {
+    const usingCookie =
+        authOptions.tokenMethod === 'cookie' ||
+        (Array.isArray(authOptions.tokenMethod) && authOptions.tokenMethod.includes('cookie'));
+    const usingBearer =
+        authOptions.tokenMethod === 'bearer' ||
+        (Array.isArray(authOptions.tokenMethod) && authOptions.tokenMethod.includes('bearer'));
+
+    if (usingCookie) {
         if (req.session) {
         if (req.session) {
             if (rememberMe) {
             if (rememberMe) {
                 req.sessionOptions.maxAge = ms('1y');
                 req.sessionOptions.maxAge = ms('1y');
             }
             }
             req.session.token = sessionToken;
             req.session.token = sessionToken;
         }
         }
-    } else {
+    }
+    if (usingBearer) {
         res.set(authOptions.authTokenHeaderKey, sessionToken);
         res.set(authOptions.authTokenHeaderKey, sessionToken);
     }
     }
 }
 }

+ 8 - 2
packages/core/src/bootstrap.ts

@@ -55,7 +55,10 @@ export async function bootstrap(userConfig: Partial<VendureConfig>): Promise<INe
     });
     });
     DefaultLogger.restoreOriginalLogLevel();
     DefaultLogger.restoreOriginalLogLevel();
     app.useLogger(new Logger());
     app.useLogger(new Logger());
-    if (config.authOptions.tokenMethod === 'cookie') {
+    const { tokenMethod } = config.authOptions;
+    const usingCookie =
+        tokenMethod === 'cookie' || (Array.isArray(tokenMethod) && tokenMethod.includes('cookie'));
+    if (usingCookie) {
         const { cookieOptions } = config.authOptions;
         const { cookieOptions } = config.authOptions;
         app.use(cookieSession(cookieOptions));
         app.use(cookieSession(cookieOptions));
     }
     }
@@ -186,7 +189,10 @@ export async function getAllEntities(userConfig: Partial<VendureConfig>): Promis
  * in the CORS options, making sure to preserve any user-configured exposedHeaders.
  * in the CORS options, making sure to preserve any user-configured exposedHeaders.
  */
  */
 function setExposedHeaders(config: Readonly<RuntimeVendureConfig>) {
 function setExposedHeaders(config: Readonly<RuntimeVendureConfig>) {
-    if (config.authOptions.tokenMethod === 'bearer') {
+    const { tokenMethod } = config.authOptions;
+    const isUsingBearerToken =
+        tokenMethod === 'bearer' || (Array.isArray(tokenMethod) && tokenMethod.includes('bearer'));
+    if (isUsingBearerToken) {
         const authTokenHeaderKey = config.authOptions.authTokenHeaderKey;
         const authTokenHeaderKey = config.authOptions.authTokenHeaderKey;
         const corsOptions = config.apiOptions.cors;
         const corsOptions = config.apiOptions.cors;
         if (typeof corsOptions !== 'boolean') {
         if (typeof corsOptions !== 'boolean') {

+ 3 - 1
packages/core/src/config/vendure-config.ts

@@ -296,9 +296,11 @@ export interface AuthOptions {
      * `authTokenHeaderKey` in the server's CORS configuration (adding `Access-Control-Expose-Headers: vendure-auth-token`
      * `authTokenHeaderKey` in the server's CORS configuration (adding `Access-Control-Expose-Headers: vendure-auth-token`
      * by default).
      * by default).
      *
      *
+     * From v1.2.0 is is possible to specify both methods as a tuple: `['cookie', 'bearer']`.
+     *
      * @default 'cookie'
      * @default 'cookie'
      */
      */
-    tokenMethod?: 'cookie' | 'bearer';
+    tokenMethod?: 'cookie' | 'bearer' | ReadonlyArray<'cookie' | 'bearer'>;
     /**
     /**
      * @description
      * @description
      * Options related to the handling of cookies when using the 'cookie' tokenMethod.
      * Options related to the handling of cookies when using the 'cookie' tokenMethod.