You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
336 lines
8.1 KiB
336 lines
8.1 KiB
// server/utils/cidaas.ts
|
|
|
|
/**
|
|
* Cidaas API Client for OAuth2/OIDC integration
|
|
*
|
|
* Provides functions to interact with Cidaas endpoints:
|
|
* - Token exchange (authorization code → access/ID tokens)
|
|
* - UserInfo fetch
|
|
* - User registration
|
|
*/
|
|
|
|
import type { H3Error } from 'h3'
|
|
|
|
/**
|
|
* Cidaas Token Response
|
|
*/
|
|
export interface CidaasTokenResponse {
|
|
access_token: string
|
|
token_type: string
|
|
expires_in: number
|
|
refresh_token?: string
|
|
id_token: string // JWT with user identity
|
|
scope: string
|
|
}
|
|
|
|
/**
|
|
* Cidaas UserInfo Response
|
|
*/
|
|
export interface CidaasUserInfo {
|
|
sub: string // Unique user ID (experimenta_id)
|
|
email: string
|
|
email_verified: boolean
|
|
given_name?: string
|
|
family_name?: string
|
|
name?: string
|
|
phone_number?: string
|
|
updated_at?: number
|
|
}
|
|
|
|
/**
|
|
* Cidaas Registration Request
|
|
*/
|
|
export interface CidaasRegistrationRequest {
|
|
email: string
|
|
password: string
|
|
given_name: string
|
|
family_name: string
|
|
locale?: string
|
|
}
|
|
|
|
/**
|
|
* Exchange authorization code for access/ID tokens
|
|
*
|
|
* @param code - Authorization code from callback
|
|
* @param codeVerifier - PKCE code verifier
|
|
* @returns Token response
|
|
* @throws H3Error if exchange fails
|
|
*/
|
|
export async function exchangeCodeForToken(
|
|
code: string,
|
|
codeVerifier: string
|
|
): Promise<CidaasTokenResponse> {
|
|
const config = useRuntimeConfig()
|
|
|
|
// Prepare token request
|
|
const params = new URLSearchParams({
|
|
grant_type: 'authorization_code',
|
|
code,
|
|
redirect_uri: config.cidaas.redirectUri,
|
|
client_id: config.cidaas.clientId,
|
|
client_secret: config.cidaas.clientSecret,
|
|
code_verifier: codeVerifier, // PKCE proof
|
|
})
|
|
|
|
try {
|
|
const response = await fetch(config.cidaas.tokenUrl, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
},
|
|
body: params.toString(),
|
|
})
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({}))
|
|
console.error('Cidaas token exchange failed:', errorData)
|
|
|
|
throw createError({
|
|
statusCode: response.status,
|
|
statusMessage: 'Token exchange failed',
|
|
data: errorData,
|
|
})
|
|
}
|
|
|
|
const tokens: CidaasTokenResponse = await response.json()
|
|
return tokens
|
|
} catch (error) {
|
|
console.error('Token exchange error:', error)
|
|
|
|
if ((error as H3Error).statusCode) {
|
|
throw error // Re-throw H3Error
|
|
}
|
|
|
|
throw createError({
|
|
statusCode: 500,
|
|
statusMessage: 'Failed to exchange authorization code',
|
|
})
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetch user info from Cidaas UserInfo endpoint
|
|
*
|
|
* @param accessToken - OAuth2 access token
|
|
* @returns User profile data
|
|
* @throws H3Error if fetch fails
|
|
*/
|
|
export async function fetchUserInfo(accessToken: string): Promise<CidaasUserInfo> {
|
|
const config = useRuntimeConfig()
|
|
|
|
try {
|
|
const response = await fetch(config.cidaas.userinfoUrl, {
|
|
method: 'GET',
|
|
headers: {
|
|
Authorization: `Bearer ${accessToken}`,
|
|
},
|
|
})
|
|
|
|
if (!response.ok) {
|
|
console.error('Cidaas UserInfo fetch failed:', response.status)
|
|
|
|
throw createError({
|
|
statusCode: response.status,
|
|
statusMessage: 'Failed to fetch user info',
|
|
})
|
|
}
|
|
|
|
const userInfo: CidaasUserInfo = await response.json()
|
|
return userInfo
|
|
} catch (error) {
|
|
console.error('UserInfo fetch error:', error)
|
|
|
|
if ((error as H3Error).statusCode) {
|
|
throw error
|
|
}
|
|
|
|
throw createError({
|
|
statusCode: 500,
|
|
statusMessage: 'Failed to fetch user information',
|
|
})
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Register new user via Cidaas Registration API
|
|
*
|
|
* @param data - Registration data
|
|
* @returns Success indicator (user must verify email before login)
|
|
* @throws H3Error if registration fails
|
|
*/
|
|
export async function registerUser(
|
|
data: CidaasRegistrationRequest
|
|
): Promise<{ success: boolean; message: string }> {
|
|
const config = useRuntimeConfig()
|
|
|
|
// Cidaas registration endpoint (adjust based on actual API)
|
|
const registrationUrl = `${config.cidaas.issuer}/users-srv/register`
|
|
|
|
try {
|
|
const response = await fetch(registrationUrl, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
email: data.email,
|
|
password: data.password,
|
|
given_name: data.given_name,
|
|
family_name: data.family_name,
|
|
locale: data.locale || 'de',
|
|
}),
|
|
})
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({}))
|
|
console.error('Cidaas registration failed:', errorData)
|
|
|
|
// Handle specific errors
|
|
if (response.status === 409) {
|
|
throw createError({
|
|
statusCode: 409,
|
|
statusMessage: 'Email already registered',
|
|
})
|
|
}
|
|
|
|
throw createError({
|
|
statusCode: response.status,
|
|
statusMessage: 'Registration failed',
|
|
data: errorData,
|
|
})
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
message: 'Registration successful. Please verify your email.',
|
|
}
|
|
} catch (error) {
|
|
console.error('Registration error:', error)
|
|
|
|
if ((error as H3Error).statusCode) {
|
|
throw error
|
|
}
|
|
|
|
throw createError({
|
|
statusCode: 500,
|
|
statusMessage: 'Failed to register user',
|
|
})
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Login with username and password (Resource Owner Password Credentials Flow)
|
|
*
|
|
* @param email - User email address
|
|
* @param password - User password
|
|
* @returns Token response with access_token and id_token
|
|
* @throws H3Error if login fails
|
|
*/
|
|
export async function loginWithPassword(
|
|
email: string,
|
|
password: string
|
|
): Promise<CidaasTokenResponse> {
|
|
const config = useRuntimeConfig()
|
|
|
|
// Prepare token request with password grant
|
|
const params = new URLSearchParams({
|
|
grant_type: 'password',
|
|
username: email, // Cidaas uses 'username' field for email
|
|
password,
|
|
client_id: config.cidaas.clientId,
|
|
client_secret: config.cidaas.clientSecret,
|
|
scope: 'openid profile email', // Request OIDC scopes
|
|
})
|
|
|
|
try {
|
|
const response = await fetch(config.cidaas.tokenUrl, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
},
|
|
body: params.toString(),
|
|
})
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({}))
|
|
console.error('Cidaas password login failed:', errorData)
|
|
|
|
// Handle specific errors
|
|
if (response.status === 401) {
|
|
throw createError({
|
|
statusCode: 401,
|
|
statusMessage: 'Invalid email or password',
|
|
})
|
|
}
|
|
|
|
throw createError({
|
|
statusCode: response.status,
|
|
statusMessage: 'Login failed',
|
|
data: errorData,
|
|
})
|
|
}
|
|
|
|
const tokens: CidaasTokenResponse = await response.json()
|
|
return tokens
|
|
} catch (error) {
|
|
console.error('Password login error:', error)
|
|
|
|
if ((error as H3Error).statusCode) {
|
|
throw error // Re-throw H3Error
|
|
}
|
|
|
|
throw createError({
|
|
statusCode: 500,
|
|
statusMessage: 'Failed to authenticate with Cidaas',
|
|
})
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Refresh access token using refresh token
|
|
*
|
|
* @param refreshToken - Refresh token from previous login
|
|
* @returns New token response
|
|
* @throws H3Error if refresh fails
|
|
*/
|
|
export async function refreshAccessToken(refreshToken: string): Promise<CidaasTokenResponse> {
|
|
const config = useRuntimeConfig()
|
|
|
|
const params = new URLSearchParams({
|
|
grant_type: 'refresh_token',
|
|
refresh_token: refreshToken,
|
|
client_id: config.cidaas.clientId,
|
|
client_secret: config.cidaas.clientSecret,
|
|
})
|
|
|
|
try {
|
|
const response = await fetch(config.cidaas.tokenUrl, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
},
|
|
body: params.toString(),
|
|
})
|
|
|
|
if (!response.ok) {
|
|
throw createError({
|
|
statusCode: response.status,
|
|
statusMessage: 'Token refresh failed',
|
|
})
|
|
}
|
|
|
|
const tokens: CidaasTokenResponse = await response.json()
|
|
return tokens
|
|
} catch (error) {
|
|
console.error('Token refresh error:', error)
|
|
|
|
if ((error as H3Error).statusCode) {
|
|
throw error
|
|
}
|
|
|
|
throw createError({
|
|
statusCode: 500,
|
|
statusMessage: 'Failed to refresh token',
|
|
})
|
|
}
|
|
}
|
|
|