Initial commit
This commit is contained in:
45
client/src/auth/authProvider.ts
Normal file
45
client/src/auth/authProvider.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { AuthProvider } from 'react-admin';
|
||||
import {
|
||||
forceReauthentication,
|
||||
getIdentity,
|
||||
getRealmRoles,
|
||||
getValidAccessToken,
|
||||
initKeycloak,
|
||||
logoutFromKeycloak,
|
||||
} from './keycloak';
|
||||
|
||||
const authProvider: AuthProvider = {
|
||||
login: async () => {
|
||||
await initKeycloak();
|
||||
},
|
||||
|
||||
logout: async () => {
|
||||
await logoutFromKeycloak();
|
||||
},
|
||||
|
||||
checkAuth: async () => {
|
||||
await getValidAccessToken();
|
||||
},
|
||||
|
||||
checkError: async (error) => {
|
||||
const status = error?.status;
|
||||
|
||||
if (status === 401) {
|
||||
await forceReauthentication();
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
if (status === 403) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
getIdentity: async () => getIdentity(),
|
||||
|
||||
getPermissions: async () => getRealmRoles(),
|
||||
};
|
||||
|
||||
export default authProvider;
|
||||
|
||||
96
client/src/auth/keycloak.ts
Normal file
96
client/src/auth/keycloak.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import Keycloak, { KeycloakTokenParsed } from 'keycloak-js';
|
||||
import { env } from '../config/env';
|
||||
|
||||
interface RealmAccessTokenParsed extends KeycloakTokenParsed {
|
||||
realm_access?: {
|
||||
roles: string[];
|
||||
};
|
||||
}
|
||||
|
||||
const keycloak = new Keycloak({
|
||||
url: env.keycloakUrl,
|
||||
realm: env.keycloakRealm,
|
||||
clientId: env.keycloakClientId,
|
||||
});
|
||||
|
||||
let keycloakInitPromise: Promise<void> | null = null;
|
||||
let refreshInFlight: Promise<void> | null = null;
|
||||
|
||||
export function getKeycloak() {
|
||||
return keycloak;
|
||||
}
|
||||
|
||||
export async function initKeycloak() {
|
||||
if (!keycloakInitPromise) {
|
||||
keycloakInitPromise = keycloak
|
||||
.init({
|
||||
onLoad: 'login-required',
|
||||
pkceMethod: 'S256',
|
||||
checkLoginIframe: false,
|
||||
})
|
||||
.then((authenticated) => {
|
||||
if (!authenticated) {
|
||||
return keycloak.login({ redirectUri: window.location.href });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await keycloakInitPromise;
|
||||
}
|
||||
|
||||
async function refreshAccessToken(minValiditySeconds = 30) {
|
||||
if (!refreshInFlight) {
|
||||
refreshInFlight = keycloak
|
||||
.updateToken(minValiditySeconds)
|
||||
.then(() => undefined)
|
||||
.finally(() => {
|
||||
refreshInFlight = null;
|
||||
});
|
||||
}
|
||||
|
||||
await refreshInFlight;
|
||||
}
|
||||
|
||||
export async function getValidAccessToken(minValiditySeconds = 30): Promise<string> {
|
||||
await initKeycloak();
|
||||
|
||||
if (!keycloak.authenticated) {
|
||||
await keycloak.login({ redirectUri: window.location.href });
|
||||
throw new Error('User is not authenticated');
|
||||
}
|
||||
|
||||
await refreshAccessToken(minValiditySeconds);
|
||||
|
||||
if (!keycloak.token) {
|
||||
throw new Error('Missing access token');
|
||||
}
|
||||
|
||||
return keycloak.token;
|
||||
}
|
||||
|
||||
export async function forceReauthentication() {
|
||||
keycloak.clearToken();
|
||||
await keycloak.login({ redirectUri: window.location.href });
|
||||
}
|
||||
|
||||
export async function logoutFromKeycloak() {
|
||||
await keycloak.logout({ redirectUri: window.location.origin });
|
||||
}
|
||||
|
||||
export function getRealmRoles(): string[] {
|
||||
const parsed = keycloak.tokenParsed as RealmAccessTokenParsed | undefined;
|
||||
const roles = parsed?.realm_access?.roles;
|
||||
return Array.isArray(roles) ? roles : [];
|
||||
}
|
||||
|
||||
export function getIdentity() {
|
||||
const parsed = keycloak.tokenParsed as RealmAccessTokenParsed | undefined;
|
||||
const id = parsed?.sub ?? 'unknown';
|
||||
const fullName =
|
||||
parsed?.name ??
|
||||
parsed?.preferred_username ??
|
||||
parsed?.email ??
|
||||
'Unknown User';
|
||||
|
||||
return { id, fullName };
|
||||
}
|
||||
Reference in New Issue
Block a user