feat: Add OIDC authentication with Authentik integration
- Add OIDC login flow with Authentik provider - Implement session-based auth with Redis store - Add avatar display from OIDC claims - Fix input field performance with react-textarea-autosize - Stabilize callbacks to prevent unnecessary re-renders - Fix history loading to skip empty session files - Add 2-row default height for input textarea 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
146
backend/utils/oidc.js
Normal file
146
backend/utils/oidc.js
Normal file
@@ -0,0 +1,146 @@
|
||||
// OIDC Client Wrapper using openid-client
|
||||
|
||||
import { Issuer, generators } from 'openid-client';
|
||||
import { authConfig } from '../config/auth.js';
|
||||
|
||||
let oidcClient = null;
|
||||
let issuer = null;
|
||||
|
||||
/**
|
||||
* Initialize the OIDC client by discovering the issuer
|
||||
*/
|
||||
export async function initializeOIDC() {
|
||||
if (!authConfig.app.authEnabled) {
|
||||
console.log('[OIDC] Authentication disabled, skipping initialization');
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(`[OIDC] Discovering issuer: ${authConfig.oidc.issuer}`);
|
||||
issuer = await Issuer.discover(authConfig.oidc.issuer);
|
||||
console.log(`[OIDC] Discovered issuer: ${issuer.issuer}`);
|
||||
|
||||
oidcClient = new issuer.Client({
|
||||
client_id: authConfig.oidc.clientId,
|
||||
client_secret: authConfig.oidc.clientSecret,
|
||||
redirect_uris: [authConfig.oidc.redirectUri],
|
||||
response_types: ['code'],
|
||||
});
|
||||
|
||||
console.log('[OIDC] Client initialized successfully');
|
||||
return oidcClient;
|
||||
} catch (error) {
|
||||
console.error('[OIDC] Failed to initialize client:', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the initialized OIDC client
|
||||
*/
|
||||
export function getClient() {
|
||||
if (!oidcClient) {
|
||||
throw new Error('OIDC client not initialized. Call initializeOIDC() first.');
|
||||
}
|
||||
return oidcClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate authorization URL for login
|
||||
*/
|
||||
export function getAuthorizationUrl(state, nonce, codeVerifier) {
|
||||
const client = getClient();
|
||||
const codeChallenge = generators.codeChallenge(codeVerifier);
|
||||
|
||||
return client.authorizationUrl({
|
||||
scope: authConfig.oidc.scopes.join(' '),
|
||||
state,
|
||||
nonce,
|
||||
code_challenge: codeChallenge,
|
||||
code_challenge_method: 'S256',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Exchange authorization code for tokens
|
||||
*/
|
||||
export async function exchangeCode(code, codeVerifier, nonce) {
|
||||
const client = getClient();
|
||||
|
||||
const tokenSet = await client.callback(
|
||||
authConfig.oidc.redirectUri,
|
||||
{ code },
|
||||
{ code_verifier: codeVerifier, nonce }
|
||||
);
|
||||
|
||||
return tokenSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and decode ID token claims
|
||||
*/
|
||||
export function getIdTokenClaims(tokenSet) {
|
||||
return tokenSet.claims();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user info from the userinfo endpoint
|
||||
*/
|
||||
export async function getUserInfo(accessToken) {
|
||||
const client = getClient();
|
||||
return await client.userinfo(accessToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh access token using refresh token
|
||||
*/
|
||||
export async function refreshTokens(refreshToken) {
|
||||
const client = getClient();
|
||||
return await client.refresh(refreshToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get end session URL for logout
|
||||
*/
|
||||
export function getEndSessionUrl(idTokenHint, postLogoutRedirectUri) {
|
||||
const client = getClient();
|
||||
return client.endSessionUrl({
|
||||
id_token_hint: idTokenHint,
|
||||
post_logout_redirect_uri: postLogoutRedirectUri,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate random state for CSRF protection
|
||||
*/
|
||||
export function generateState() {
|
||||
return generators.state();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate random nonce for ID token validation
|
||||
*/
|
||||
export function generateNonce() {
|
||||
return generators.nonce();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate code verifier for PKCE
|
||||
*/
|
||||
export function generateCodeVerifier() {
|
||||
return generators.codeVerifier();
|
||||
}
|
||||
|
||||
export default {
|
||||
initializeOIDC,
|
||||
getClient,
|
||||
getAuthorizationUrl,
|
||||
exchangeCode,
|
||||
getIdTokenClaims,
|
||||
getUserInfo,
|
||||
refreshTokens,
|
||||
getEndSessionUrl,
|
||||
generateState,
|
||||
generateNonce,
|
||||
generateCodeVerifier,
|
||||
};
|
||||
Reference in New Issue
Block a user