diff --git a/src/lib/stores/auth-user.ts b/src/lib/stores/auth-user.ts index 300e37ade..be029ae6a 100644 --- a/src/lib/stores/auth-user.ts +++ b/src/lib/stores/auth-user.ts @@ -13,6 +13,7 @@ export const setAuthUser = (user: User, oidcFlow: OIDCFlow) => { switch (oidcFlow) { case OIDCFlow.AuthorizationCode: + default: if (!accessToken) { throw new Error('No access token'); } diff --git a/src/lib/utilities/is-authorized.test.ts b/src/lib/utilities/is-authorized.test.ts index cb25b5507..9046457e1 100644 --- a/src/lib/utilities/is-authorized.test.ts +++ b/src/lib/utilities/is-authorized.test.ts @@ -2,20 +2,24 @@ import { describe, expect, it } from 'vitest'; import { isAuthorized } from './is-authorized'; -const user = { - accessToken: 'xxx', +const codeUser = { + accessToken: 'accessToken', +}; + +const implicitUser = { + idToken: 'idToken', }; const noUser = { name: 'name', email: 'email', picture: 'picture', - idToken: 'idToken', }; -const getSettings = (enabled: boolean) => ({ +const getSettings = (enabled: boolean, flow: string) => ({ auth: { enabled, + flow, options: [], }, baseUrl: 'www.base.com', @@ -29,16 +33,40 @@ const getSettings = (enabled: boolean) => ({ }); describe('isAuthorized', () => { - it('should return true if auth no enabled and no user', () => { - expect(isAuthorized(getSettings(false), noUser)).toBe(true); + it('should return true if auth not enabled and no user', () => { + expect(isAuthorized(getSettings(false, 'authorization-code'), noUser)).toBe( + true, + ); + }); + it('should return true if auth not enabled and user', () => { + expect( + isAuthorized(getSettings(false, 'authorization-code'), codeUser), + ).toBe(true); + }); + it('should return false if code auth enabled and no user', () => { + expect(isAuthorized(getSettings(true, 'authorization-code'), noUser)).toBe( + false, + ); + }); + it('should return false if code auth enabled and implicit user', () => { + expect( + isAuthorized(getSettings(true, 'authorization-code'), implicitUser), + ).toBe(false); + }); + it('should return true if code auth enabled and code user', () => { + expect( + isAuthorized(getSettings(true, 'authorization-code'), codeUser), + ).toBe(true); }); - it('should return true if auth no enabled and user', () => { - expect(isAuthorized(getSettings(false), user)).toBe(true); + it('should return false if implicit auth enabled and no user', () => { + expect(isAuthorized(getSettings(true, 'implicit'), noUser)).toBe(false); }); - it('should return false if auth enabled and no user', () => { - expect(isAuthorized(getSettings(true), noUser)).toBe(false); + it('should return false if implicit auth enabled and implicit user', () => { + expect(isAuthorized(getSettings(true, 'implicit'), codeUser)).toBe(false); }); - it('should return true if auth enabled and user', () => { - expect(isAuthorized(getSettings(true), user)).toBe(true); + it('should return true if implicit auth enabled and implicit user', () => { + expect(isAuthorized(getSettings(true, 'implicit'), implicitUser)).toBe( + true, + ); }); }); diff --git a/src/lib/utilities/request-from-api.test.ts b/src/lib/utilities/request-from-api.test.ts index 3f7c3fcf4..1bda98aed 100644 --- a/src/lib/utilities/request-from-api.test.ts +++ b/src/lib/utilities/request-from-api.test.ts @@ -110,7 +110,7 @@ describe('requestFromAPI', () => { }); }); - it('should not add csrf cookie to headers if not presdent', async () => { + it('should not add csrf cookie to headers if not present', async () => { const token = 'token'; const request = fetchMock(); diff --git a/src/lib/utilities/route-for.test.ts b/src/lib/utilities/route-for.test.ts index 939372f9e..5724a4450 100644 --- a/src/lib/utilities/route-for.test.ts +++ b/src/lib/utilities/route-for.test.ts @@ -172,6 +172,7 @@ describe('routeFor SSO authentication ', () => { it('Options added through settings should be passed in the url', () => { const settings = { auth: { + flow: 'authorization-code', options: ['one'], }, baseUrl: 'https://localhost/', @@ -192,6 +193,7 @@ describe('routeFor SSO authentication ', () => { it('should fallback to the originUrl if returnUrl is not provided', () => { const settings = { auth: { + flow: 'authorization-code', options: ['one'], }, baseUrl: 'https://localhost/', @@ -210,6 +212,7 @@ describe('routeFor SSO authentication ', () => { it('should use the returnUrl if provided', () => { const settings = { auth: { + flow: 'authorization-code', options: ['one'], }, baseUrl: 'https://localhost/', @@ -229,6 +232,7 @@ describe('routeFor SSO authentication ', () => { it("should not add the options from the search param if they don't exist in the current url params", () => { const settings = { auth: { + flow: 'authorization-code', options: ['one'], }, baseUrl: 'https://localhost/', @@ -245,7 +249,10 @@ describe('routeFor SSO authentication ', () => { }); it('Should render a login url', () => { - const settings = { auth: {}, baseUrl: 'https://localhost' }; + const settings = { + auth: { flow: 'authorization-code' }, + baseUrl: 'https://localhost', + }; const searchParams = new URLSearchParams(); const sso = routeForAuthentication({ settings, searchParams }); @@ -254,7 +261,10 @@ describe('routeFor SSO authentication ', () => { }); it('Should add return URL search param', () => { - const settings = { auth: {}, baseUrl: 'https://localhost' }; + const settings = { + auth: { flow: 'authorization-code' }, + baseUrl: 'https://localhost', + }; const searchParams = new URLSearchParams(); searchParams.set('returnUrl', 'https://localhost/some/path'); @@ -271,7 +281,10 @@ describe('routeFor SSO authentication ', () => { }); it('Should not add return URL search param if undefined', () => { - const settings = { auth: {}, baseUrl: 'https://localhost' }; + const settings = { + auth: { flow: 'authorization-code' }, + baseUrl: 'https://localhost', + }; const searchParams = new URLSearchParams(); const sso = routeForAuthentication({ settings, searchParams }); @@ -283,6 +296,7 @@ describe('routeFor SSO authentication ', () => { it('test of the signin flow', () => { const settings = { auth: { + flow: 'authorization-code', options: ['organization_name', 'invitation'], }, baseUrl: 'https://localhost/', @@ -302,6 +316,56 @@ describe('routeFor SSO authentication ', () => { ); }); + describe('implicit oidc flow', () => { + it('should add a nonce', () => { + const settings = { + auth: { + flow: 'implicit', + authorizationUrl: 'https://accounts.google.com/o/oauth2/v2/auth', + scopes: ['openid', 'email', 'profile'], + }, + baseUrl: 'https://localhost', + }; + + const searchParams = new URLSearchParams(); + + const sso = routeForAuthentication({ + settings, + searchParams, + }); + + const ssoUrl = new URL(sso); + expect(window.localStorage.getItem('nonce')).toBe( + ssoUrl.searchParams.get('nonce'), + ); + }); + + it('should manage state', () => { + const settings = { + auth: { + flow: 'implicit', + authorizationUrl: 'https://accounts.google.com/o/oauth2/v2/auth', + scopes: ['openid', 'email', 'profile'], + }, + baseUrl: 'https://localhost', + }; + + const searchParams = new URLSearchParams(); + searchParams.set('returnUrl', 'https://localhost/some/path'); + + const sso = routeForAuthentication({ + settings, + searchParams, + }); + + const ssoUrlStateKey = new URL(sso).searchParams.get('state'); + expect(ssoUrlStateKey).not.toBeNull(); + expect(window.sessionStorage.getItem(ssoUrlStateKey as string)).toBe( + 'https://localhost/some/path', + ); + }); + }); + describe('routeForLoginPage', () => { afterEach(() => { vi.clearAllMocks(); diff --git a/src/lib/utilities/route-for.ts b/src/lib/utilities/route-for.ts index 8b92619d6..ab4d1f361 100644 --- a/src/lib/utilities/route-for.ts +++ b/src/lib/utilities/route-for.ts @@ -188,6 +188,7 @@ export const routeForAuthentication = ( const { settings, searchParams: currentSearchParams, originUrl } = parameters; switch (settings.auth.flow) { case OIDCFlow.AuthorizationCode: + default: return routeForAuthorizationCodeFlow( settings, currentSearchParams, @@ -298,6 +299,11 @@ export const routeForOIDCImplicitCallback = (): string => { // TODO: support optional issuer validation with settings.auth.issuerUrl and token.iss + if (!token.nonce) { + throw new OIDCImplicitCallbackError('No nonce in token'); + } else if (token.nonce !== nonce) { + throw new OIDCImplicitCallbackError('Mismatched nonces'); + } if (!token.nonce) { throw new OIDCImplicitCallbackError('No nonce in token'); } else if (token.nonce !== nonce) {