diff --git a/.github/workflows/auth-end-to-end-tests.yml b/.github/workflows/auth-end-to-end-tests.yml index a8dd57235..a780dacee 100644 --- a/.github/workflows/auth-end-to-end-tests.yml +++ b/.github/workflows/auth-end-to-end-tests.yml @@ -26,7 +26,7 @@ jobs: - uses: pnpm/action-setup@v4.0.0 with: - version: 9.11.0 + version: 9.15.3 - uses: actions/setup-node@v4 with: diff --git a/.github/workflows/auth-ui-end-to-end-tests.yml b/.github/workflows/auth-ui-end-to-end-tests.yml index 42f0ca4c5..331b241d4 100644 --- a/.github/workflows/auth-ui-end-to-end-tests.yml +++ b/.github/workflows/auth-ui-end-to-end-tests.yml @@ -26,7 +26,7 @@ jobs: - uses: pnpm/action-setup@v4.0.0 with: - version: 9.11.0 + version: 9.15.3 - uses: actions/setup-node@v4 with: diff --git a/.github/workflows/federation-tests.yml b/.github/workflows/federation-tests.yml index ff2fc9996..f199469c8 100644 --- a/.github/workflows/federation-tests.yml +++ b/.github/workflows/federation-tests.yml @@ -26,7 +26,7 @@ jobs: - uses: pnpm/action-setup@v4.0.0 with: - version: 9.11.0 + version: 9.15.3 - uses: actions/setup-node@v4 with: diff --git a/.github/workflows/mysql-end-to-end-tests.yml b/.github/workflows/mysql-end-to-end-tests.yml index cc16e0f72..af47c2f31 100644 --- a/.github/workflows/mysql-end-to-end-tests.yml +++ b/.github/workflows/mysql-end-to-end-tests.yml @@ -34,7 +34,7 @@ jobs: - uses: pnpm/action-setup@v4.0.0 with: - version: 9.11.0 + version: 9.15.3 - uses: actions/setup-node@v4 with: diff --git a/.github/workflows/other-end-to-end-tests.yml b/.github/workflows/other-end-to-end-tests.yml index 95b45ab65..e64a31798 100644 --- a/.github/workflows/other-end-to-end-tests.yml +++ b/.github/workflows/other-end-to-end-tests.yml @@ -26,7 +26,7 @@ jobs: - uses: pnpm/action-setup@v4.0.0 with: - version: 9.11.0 + version: 9.15.3 - uses: actions/setup-node@v4 with: diff --git a/.github/workflows/postgres-end-to-end-tests.yml b/.github/workflows/postgres-end-to-end-tests.yml index c5f2e9b20..42fee9879 100644 --- a/.github/workflows/postgres-end-to-end-tests.yml +++ b/.github/workflows/postgres-end-to-end-tests.yml @@ -41,7 +41,7 @@ jobs: - uses: pnpm/action-setup@v4.0.0 with: - version: 9.11.0 + version: 9.15.3 - uses: actions/setup-node@v4 with: diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 53e7d8c29..3e2b9af6e 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -31,7 +31,7 @@ jobs: - uses: pnpm/action-setup@v4.0.0 with: - version: 9.11.0 + version: 9.15.3 - name: Setup Node.js uses: actions/setup-node@v4 diff --git a/.github/workflows/rest-ui-end-to-end-tests.yml b/.github/workflows/rest-ui-end-to-end-tests.yml index 08b49bdbe..f0270dbf2 100644 --- a/.github/workflows/rest-ui-end-to-end-tests.yml +++ b/.github/workflows/rest-ui-end-to-end-tests.yml @@ -26,7 +26,7 @@ jobs: - uses: pnpm/action-setup@v4.0.0 with: - version: 9.11.0 + version: 9.15.3 - uses: actions/setup-node@v4 with: diff --git a/.github/workflows/sqlite-end-to-end-tests.yml b/.github/workflows/sqlite-end-to-end-tests.yml index b287a8cd7..991e492f1 100644 --- a/.github/workflows/sqlite-end-to-end-tests.yml +++ b/.github/workflows/sqlite-end-to-end-tests.yml @@ -26,7 +26,7 @@ jobs: - uses: pnpm/action-setup@v4.0.0 with: - version: 9.11.0 + version: 9.15.3 - uses: actions/setup-node@v4 with: diff --git a/.github/workflows/storage-provider-end-to-end-tests.yml b/.github/workflows/storage-provider-end-to-end-tests.yml index 0f3347659..153de7de2 100644 --- a/.github/workflows/storage-provider-end-to-end-tests.yml +++ b/.github/workflows/storage-provider-end-to-end-tests.yml @@ -50,7 +50,7 @@ jobs: - uses: pnpm/action-setup@v4.0.0 with: - version: 9.11.0 + version: 9.15.3 - uses: actions/setup-node@v4 with: diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index af1b43dc5..80434debd 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -26,7 +26,7 @@ jobs: - uses: pnpm/action-setup@v4.0.0 with: - version: 9.11.0 + version: 9.15.3 - uses: actions/setup-node@v4 with: diff --git a/.github/workflows/windows-end-to-end-tests.yml b/.github/workflows/windows-end-to-end-tests.yml index 4705f8ac9..5b21bbb91 100644 --- a/.github/workflows/windows-end-to-end-tests.yml +++ b/.github/workflows/windows-end-to-end-tests.yml @@ -25,7 +25,7 @@ jobs: - uses: pnpm/action-setup@v4.0.0 with: - version: 9.11.0 + version: 9.15.3 - uses: actions/setup-node@v4 with: diff --git a/src/packages/auth/src/authentication/apollo/plugin.ts b/src/packages/auth/src/authentication/apollo/plugin.ts index ad3c1fc67..8826b5d4f 100644 --- a/src/packages/auth/src/authentication/apollo/plugin.ts +++ b/src/packages/auth/src/authentication/apollo/plugin.ts @@ -171,9 +171,13 @@ export const authApolloPlugin = ( }); if (!apiKey || !apiKey.secret) { - apiKeyVerificationFailedMessage = 'Bad Request: API Key Authentication Failed. (E0001)'; + apiKeyVerificationFailedMessage = 'Bad Request: API Key Authentication Failed.'; + logger.error( + `API Key Authentication Failed. No API Key was received, or it had no secret.` + ); } else if (apiKey.revoked) { - apiKeyVerificationFailedMessage = 'Bad Request: API Key Authentication Failed. (E0002)'; + apiKeyVerificationFailedMessage = 'Bad Request: API Key Authentication Failed.'; + logger.error({ apiKey }, `API Key Authentication Failed. API Key is revoked.`); } else if (await verifyPassword(secret, apiKey.secret)) { contextValue.user = new UserProfile({ id: String(apiKey.id), @@ -183,7 +187,11 @@ export const authApolloPlugin = ( contextValue.token = {}; upsertAuthorizationContext(contextValue); } else { - apiKeyVerificationFailedMessage = 'Bad Request: API Key Authentication Failed. (E0003)'; + apiKeyVerificationFailedMessage = 'Bad Request: API Key Authentication Failed.'; + logger.error( + { apiKey }, + `API Key Authentication Failed. Verify password call did not succeed.` + ); } } else { // Ok, we are working in token land at this point. We either have the following scenarios: @@ -229,7 +237,16 @@ export const authApolloPlugin = ( upsertAuthorizationContext(contextValue); } catch (err: unknown) { - logger.error({ err }, 'JWT verification failed.'); + logger.error({ err }, 'JWT verification failed, treating as guest.'); + + // We are a guest and have not logged in yet. + contextValue.user = new UserProfile({ + id: undefined, + roles: ['GUEST'], + }); + upsertAuthorizationContext(contextValue); + + // But we still got an error and need to tell the client to redirect to login. tokenVerificationFailed = true; } } @@ -246,9 +263,6 @@ export const authApolloPlugin = ( if (apiKeyVerificationFailedMessage) { throw new AuthenticationError(apiKeyVerificationFailedMessage); } - if (tokenVerificationFailed) { - throw new AuthenticationError('Unauthorized: Token verification failed.'); - } }, willSendResponse: async ({ response, contextValue }) => { @@ -267,7 +281,7 @@ export const authApolloPlugin = ( } } - //If we received a challenge error we need to redirect, set the header to tell the client to do so. + // If we received a challenge error we need to redirect, set the header to tell the client to do so. if (errors?.some(didEncounterChallengeError)) { logger.trace('Forbidden Error Found: setting X-Auth-Redirect header.'); @@ -288,8 +302,9 @@ export const authApolloPlugin = ( ); } - // Let's check if verification has failed and redirect to login if it has - if (tokenVerificationFailed) { + // Let's check if verification has failed and redirect to login if it has, but only if this + // happens when we're not actually trying to log in. + if (tokenVerificationFailed && !contextValue.skipLoginRedirect) { logger.trace('JWT verification failed: setting X-Auth-Redirect header.'); response.http.status = 200; response.http.headers.set( diff --git a/src/packages/auth/src/authentication/methods/magic-link.ts b/src/packages/auth/src/authentication/methods/magic-link.ts index b9609d6c4..098965a19 100644 --- a/src/packages/auth/src/authentication/methods/magic-link.ts +++ b/src/packages/auth/src/authentication/methods/magic-link.ts @@ -216,8 +216,15 @@ export class MagicLink extends BaseAuthMethod { async verifyLoginMagicLink({ args: { username, token }, - }: ResolverOptions<{ username: string; token: string }>): Promise { - return this.verifyMagicLink(username, token); + context, + }: ResolverOptions<{ username: string; token: string }, AuthorizationContext>): Promise { + const result = await this.verifyMagicLink(username, token); + + // If they have an expired token while successfully trying to verify a magic link, they don't actually need to get + // redirected to login. + context.skipLoginRedirect = true; + + return result; } async sendChallengeMagicLink({ diff --git a/src/packages/auth/src/authentication/methods/password.ts b/src/packages/auth/src/authentication/methods/password.ts index 9d33246a6..a7496453f 100644 --- a/src/packages/auth/src/authentication/methods/password.ts +++ b/src/packages/auth/src/authentication/methods/password.ts @@ -354,6 +354,12 @@ export class Password extends BaseAuthMethod { if (!userProfile) throw new AuthenticationError('Login unsuccessful: Authentication failed.'); const authToken = await tokenProvider.generateToken(userProfile); + + // This allows people with expired tokens to not get redirected to login while trying to log in. + // This happens at the end of this function so that we can be sure all the other checks have passed + // before we say it's ok not to redirect to login. + context.skipLoginRedirect = true; + return authToken; } diff --git a/src/packages/auth/src/types.ts b/src/packages/auth/src/types.ts index 223f5bfd8..d169684c4 100644 --- a/src/packages/auth/src/types.ts +++ b/src/packages/auth/src/types.ts @@ -24,6 +24,7 @@ export interface AuthorizationContext extends BaseContext { token?: string | JwtPayload; user?: UserProfile; redirectUri?: URL; + skipLoginRedirect?: boolean; } export enum AccessType { diff --git a/src/packages/end-to-end/src/__tests__/api/auth/api-key/api-key.test.ts b/src/packages/end-to-end/src/__tests__/api/auth/api-key/api-key.test.ts index 8ed793055..f77a63375 100644 --- a/src/packages/end-to-end/src/__tests__/api/auth/api-key/api-key.test.ts +++ b/src/packages/end-to-end/src/__tests__/api/auth/api-key/api-key.test.ts @@ -109,7 +109,7 @@ describe('API Key Authentication', () => { assert(response.body.kind === 'single'); expect(response.body.singleResult.errors).toBeDefined(); expect(response.body.singleResult.errors?.[0]?.message).toBe( - 'Bad Request: API Key Authentication Failed. (E0001)' + 'Bad Request: API Key Authentication Failed.' ); }); @@ -130,7 +130,7 @@ describe('API Key Authentication', () => { assert(response.body.kind === 'single'); expect(response.body.singleResult.errors).toBeDefined(); expect(response.body.singleResult.errors?.[0]?.message).toBe( - 'Bad Request: API Key Authentication Failed. (E0002)' + 'Bad Request: API Key Authentication Failed.' ); }); @@ -151,7 +151,7 @@ describe('API Key Authentication', () => { assert(response.body.kind === 'single'); expect(response.body.singleResult.errors).toBeDefined(); expect(response.body.singleResult.errors?.[0]?.message).toBe( - 'Bad Request: API Key Authentication Failed. (E0003)' + 'Bad Request: API Key Authentication Failed.' ); }); diff --git a/src/packages/end-to-end/src/__tests__/api/auth/security/invalid-token.test.ts b/src/packages/end-to-end/src/__tests__/api/auth/security/invalid-token.test.ts index c3af6f911..2f9fdd5f1 100644 --- a/src/packages/end-to-end/src/__tests__/api/auth/security/invalid-token.test.ts +++ b/src/packages/end-to-end/src/__tests__/api/auth/security/invalid-token.test.ts @@ -74,9 +74,7 @@ describe('Security', () => { }); assert(response.body.kind === 'single'); - expect(response.body.singleResult.errors?.[0]?.message).toEqual( - 'Unauthorized: Token verification failed.' - ); + expect(response.body.singleResult.data).toEqual({ tags: null }); expect(response.http.headers.get('X-Auth-Redirect')).toBe( `${process.env.AUTH_BASE_URI}/auth/login?redirect_uri=${encodeURIComponent( process.env.AUTH_BASE_URI + '/'