import { authorize, refresh, revoke } from 'react-native-app-auth'; import Config from '../../config'; import apiClient, { registerTokenRefresh } from './client'; import { authenticateWithCredentials } from './cognito-auth'; import { tokenStorage, userStorage, StoredTokens, StoredUser, } from '../storage'; function parseUserFromIdToken(idToken: string): StoredUser { const base64Url = idToken.split('.')[1]; const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); const padded = base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), '='); const payload = JSON.parse(atob(padded)); const groups: string[] = payload['cognito:groups'] || []; const role = groups.includes('sysadmins') ? 'SysAdmin' : groups.includes('admins') ? 'Admin' : 'Dispatcher'; return { id: payload.sub, email: payload.email, displayName: payload.name || payload.email, role: role as StoredUser['role'], }; } const cognitoConfig = { clientId: Config.COGNITO_CLIENT_ID, redirectUrl: Config.COGNITO_REDIRECT_URI, scopes: Config.COGNITO_SCOPES, serviceConfiguration: { authorizationEndpoint: `https://${Config.COGNITO_DOMAIN}/oauth2/authorize`, tokenEndpoint: `https://${Config.COGNITO_DOMAIN}/oauth2/token`, revocationEndpoint: `https://${Config.COGNITO_DOMAIN}/oauth2/revoke`, }, usePKCE: true, iosPrefersEphemeralSession: false, }; export const authApi = { login: async (): Promise => { const result = await authorize(cognitoConfig); const tokens: StoredTokens = { accessToken: result.accessToken, idToken: result.idToken, refreshToken: result.refreshToken, expiresAt: result.accessTokenExpirationDate, }; await tokenStorage.save(tokens); const profile = await authApi.getMe(); await userStorage.save(profile); return profile; }, loginWithCredentials: async ( email: string, password: string, ): Promise => { const cognitoTokens = await authenticateWithCredentials(email, password); const tokens: StoredTokens = { accessToken: cognitoTokens.accessToken, idToken: cognitoTokens.idToken, refreshToken: cognitoTokens.refreshToken, expiresAt: cognitoTokens.expiresAt, }; await tokenStorage.save(tokens); let profile: StoredUser; try { profile = await authApi.getMe(); } catch { profile = parseUserFromIdToken(cognitoTokens.idToken); } await userStorage.save(profile); return profile; }, refreshTokens: async (): Promise => { const stored = await tokenStorage.get(); if (!stored?.refreshToken) throw new Error('No refresh token'); const result = await refresh(cognitoConfig, { refreshToken: stored.refreshToken, }); await tokenStorage.save({ accessToken: result.accessToken, idToken: result.idToken || stored.idToken, refreshToken: result.refreshToken || stored.refreshToken, expiresAt: result.accessTokenExpirationDate, }); }, logout: async (): Promise => { const stored = await tokenStorage.get(); if (stored?.accessToken) { try { await revoke(cognitoConfig, { tokenToRevoke: stored.accessToken, includeBasicAuth: false, sendClientId: true, }); } catch { // Best-effort revocation } } await tokenStorage.clear(); await userStorage.clear(); }, getMe: async (): Promise => { const res = await apiClient.get('/users/me'); return res.data; }, }; registerTokenRefresh(() => authApi.refreshTokens());