feat(auth): add WorkOS and Keycloak SSO providers
This commit is contained in:
@@ -6,14 +6,15 @@ describe('buildOAuthProviders', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
process.env = { ...originalEnv };
|
||||
// Clear all SSO-related env vars before each test
|
||||
delete process.env['AUTHENTIK_CLIENT_ID'];
|
||||
delete process.env['AUTHENTIK_CLIENT_SECRET'];
|
||||
delete process.env['AUTHENTIK_ISSUER'];
|
||||
delete process.env['WORKOS_CLIENT_ID'];
|
||||
delete process.env['WORKOS_CLIENT_SECRET'];
|
||||
delete process.env['WORKOS_ISSUER'];
|
||||
delete process.env['KEYCLOAK_CLIENT_ID'];
|
||||
delete process.env['KEYCLOAK_CLIENT_SECRET'];
|
||||
delete process.env['KEYCLOAK_ISSUER'];
|
||||
delete process.env['KEYCLOAK_URL'];
|
||||
delete process.env['KEYCLOAK_REALM'];
|
||||
});
|
||||
@@ -28,22 +29,32 @@ describe('buildOAuthProviders', () => {
|
||||
});
|
||||
|
||||
describe('WorkOS', () => {
|
||||
it('includes workos provider when WORKOS_CLIENT_ID is set', () => {
|
||||
it('includes workos provider when all required env vars are set', () => {
|
||||
process.env['WORKOS_CLIENT_ID'] = 'client_test123';
|
||||
process.env['WORKOS_CLIENT_SECRET'] = 'sk_live_test';
|
||||
process.env['WORKOS_ISSUER'] = 'https://example.authkit.app/';
|
||||
|
||||
const providers = buildOAuthProviders();
|
||||
const workos = providers.find((p) => p.providerId === 'workos');
|
||||
|
||||
expect(workos).toBeDefined();
|
||||
expect(workos?.clientId).toBe('client_test123');
|
||||
expect(workos?.authorizationUrl).toBe('https://api.workos.com/sso/authorize');
|
||||
expect(workos?.tokenUrl).toBe('https://api.workos.com/sso/token');
|
||||
expect(workos?.userInfoUrl).toBe('https://api.workos.com/sso/profile');
|
||||
expect(workos?.issuer).toBe('https://example.authkit.app');
|
||||
expect(workos?.discoveryUrl).toBe(
|
||||
'https://example.authkit.app/.well-known/openid-configuration',
|
||||
);
|
||||
expect(workos?.scopes).toEqual(['openid', 'email', 'profile']);
|
||||
});
|
||||
|
||||
it('excludes workos provider when WORKOS_CLIENT_ID is not set', () => {
|
||||
it('throws when WorkOS is partially configured', () => {
|
||||
process.env['WORKOS_CLIENT_ID'] = 'client_test123';
|
||||
|
||||
expect(() => buildOAuthProviders()).toThrow(
|
||||
'@mosaic/auth: WorkOS SSO requires WORKOS_ISSUER, WORKOS_CLIENT_ID, WORKOS_CLIENT_SECRET.',
|
||||
);
|
||||
});
|
||||
|
||||
it('excludes workos provider when WorkOS is not configured', () => {
|
||||
const providers = buildOAuthProviders();
|
||||
const workos = providers.find((p) => p.providerId === 'workos');
|
||||
expect(workos).toBeUndefined();
|
||||
@@ -51,47 +62,78 @@ describe('buildOAuthProviders', () => {
|
||||
});
|
||||
|
||||
describe('Keycloak', () => {
|
||||
it('includes keycloak provider when KEYCLOAK_CLIENT_ID is set', () => {
|
||||
it('includes keycloak provider when KEYCLOAK_ISSUER is set', () => {
|
||||
process.env['KEYCLOAK_CLIENT_ID'] = 'mosaic';
|
||||
process.env['KEYCLOAK_CLIENT_SECRET'] = 'secret123';
|
||||
process.env['KEYCLOAK_URL'] = 'https://auth.example.com';
|
||||
process.env['KEYCLOAK_ISSUER'] = 'https://auth.example.com/realms/myrealm/';
|
||||
|
||||
const providers = buildOAuthProviders();
|
||||
const keycloakProvider = providers.find((p) => p.providerId === 'keycloak');
|
||||
|
||||
expect(keycloakProvider).toBeDefined();
|
||||
expect(keycloakProvider?.clientId).toBe('mosaic');
|
||||
expect(keycloakProvider?.discoveryUrl).toBe(
|
||||
'https://auth.example.com/realms/myrealm/.well-known/openid-configuration',
|
||||
);
|
||||
expect(keycloakProvider?.scopes).toEqual(['openid', 'email', 'profile']);
|
||||
});
|
||||
|
||||
it('supports deriving the Keycloak issuer from KEYCLOAK_URL and KEYCLOAK_REALM', () => {
|
||||
process.env['KEYCLOAK_CLIENT_ID'] = 'mosaic';
|
||||
process.env['KEYCLOAK_CLIENT_SECRET'] = 'secret123';
|
||||
process.env['KEYCLOAK_URL'] = 'https://auth.example.com/';
|
||||
process.env['KEYCLOAK_REALM'] = 'myrealm';
|
||||
|
||||
const providers = buildOAuthProviders();
|
||||
const keycloak = providers.find((p) => p.providerId === 'keycloak');
|
||||
const keycloakProvider = providers.find((p) => p.providerId === 'keycloak');
|
||||
|
||||
expect(keycloak).toBeDefined();
|
||||
expect(keycloak?.clientId).toBe('mosaic');
|
||||
expect(keycloak?.discoveryUrl).toBe(
|
||||
expect(keycloakProvider?.discoveryUrl).toBe(
|
||||
'https://auth.example.com/realms/myrealm/.well-known/openid-configuration',
|
||||
);
|
||||
expect(keycloak?.scopes).toEqual(['openid', 'email', 'profile']);
|
||||
});
|
||||
|
||||
it('excludes keycloak provider when KEYCLOAK_CLIENT_ID is not set', () => {
|
||||
it('throws when Keycloak is partially configured', () => {
|
||||
process.env['KEYCLOAK_CLIENT_ID'] = 'mosaic';
|
||||
process.env['KEYCLOAK_CLIENT_SECRET'] = 'secret123';
|
||||
|
||||
expect(() => buildOAuthProviders()).toThrow(
|
||||
'@mosaic/auth: Keycloak SSO requires KEYCLOAK_CLIENT_ID, KEYCLOAK_CLIENT_SECRET, KEYCLOAK_ISSUER.',
|
||||
);
|
||||
});
|
||||
|
||||
it('excludes keycloak provider when Keycloak is not configured', () => {
|
||||
const providers = buildOAuthProviders();
|
||||
const keycloak = providers.find((p) => p.providerId === 'keycloak');
|
||||
expect(keycloak).toBeUndefined();
|
||||
const keycloakProvider = providers.find((p) => p.providerId === 'keycloak');
|
||||
expect(keycloakProvider).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Authentik', () => {
|
||||
it('includes authentik provider when AUTHENTIK_CLIENT_ID is set', () => {
|
||||
it('includes authentik provider when all required env vars are set', () => {
|
||||
process.env['AUTHENTIK_CLIENT_ID'] = 'authentik-client';
|
||||
process.env['AUTHENTIK_CLIENT_SECRET'] = 'authentik-secret';
|
||||
process.env['AUTHENTIK_ISSUER'] = 'https://auth.example.com/application/o/mosaic';
|
||||
process.env['AUTHENTIK_ISSUER'] = 'https://auth.example.com/application/o/mosaic/';
|
||||
|
||||
const providers = buildOAuthProviders();
|
||||
const authentik = providers.find((p) => p.providerId === 'authentik');
|
||||
|
||||
expect(authentik).toBeDefined();
|
||||
expect(authentik?.clientId).toBe('authentik-client');
|
||||
expect(authentik?.issuer).toBe('https://auth.example.com/application/o/mosaic');
|
||||
expect(authentik?.discoveryUrl).toBe(
|
||||
'https://auth.example.com/application/o/mosaic/.well-known/openid-configuration',
|
||||
);
|
||||
});
|
||||
|
||||
it('excludes authentik provider when AUTHENTIK_CLIENT_ID is not set', () => {
|
||||
it('throws when Authentik is partially configured', () => {
|
||||
process.env['AUTHENTIK_CLIENT_ID'] = 'authentik-client';
|
||||
|
||||
expect(() => buildOAuthProviders()).toThrow(
|
||||
'@mosaic/auth: Authentik SSO requires AUTHENTIK_ISSUER, AUTHENTIK_CLIENT_ID, AUTHENTIK_CLIENT_SECRET.',
|
||||
);
|
||||
});
|
||||
|
||||
it('excludes authentik provider when Authentik is not configured', () => {
|
||||
const providers = buildOAuthProviders();
|
||||
const authentik = providers.find((p) => p.providerId === 'authentik');
|
||||
expect(authentik).toBeUndefined();
|
||||
@@ -100,10 +142,14 @@ describe('buildOAuthProviders', () => {
|
||||
|
||||
it('registers all three providers when all env vars are set', () => {
|
||||
process.env['AUTHENTIK_CLIENT_ID'] = 'a-id';
|
||||
process.env['AUTHENTIK_CLIENT_SECRET'] = 'a-secret';
|
||||
process.env['AUTHENTIK_ISSUER'] = 'https://auth.example.com/application/o/mosaic';
|
||||
process.env['WORKOS_CLIENT_ID'] = 'w-id';
|
||||
process.env['WORKOS_CLIENT_SECRET'] = 'w-secret';
|
||||
process.env['WORKOS_ISSUER'] = 'https://example.authkit.app';
|
||||
process.env['KEYCLOAK_CLIENT_ID'] = 'k-id';
|
||||
process.env['KEYCLOAK_URL'] = 'https://kc.example.com';
|
||||
process.env['KEYCLOAK_REALM'] = 'test';
|
||||
process.env['KEYCLOAK_CLIENT_SECRET'] = 'k-secret';
|
||||
process.env['KEYCLOAK_ISSUER'] = 'https://kc.example.com/realms/test';
|
||||
|
||||
const providers = buildOAuthProviders();
|
||||
expect(providers).toHaveLength(3);
|
||||
|
||||
Reference in New Issue
Block a user