gabrielb a034446cf1
All checks were successful
Dev Build & Deploy Portal / build-deploy (push) Successful in 2m34s
feat(auth): adicionar suporte para temas e modo escuro na autenticação
2026-05-20 21:14:08 -03:00

108 lines
3.3 KiB
TypeScript

export interface TokenResponse {
access_token: string
refresh_token: string
id_token: string
expires_in: number
refresh_expires_in?: number
token_type: string
scope?: string
}
function realmBase(): string {
const cfg = useRuntimeConfig()
return `${cfg.keycloakUrl}/realms/${cfg.keycloakRealm}`
}
export function buildAuthUrl(opts: {
codeChallenge: string
state: string
redirectUri: string
loginHint?: string
primary?: string
dark?: boolean
}): string {
const cfg = useRuntimeConfig()
const params = new URLSearchParams({
client_id: cfg.keycloakClientId,
redirect_uri: opts.redirectUri,
response_type: 'code',
scope: 'openid profile email',
code_challenge: opts.codeChallenge,
code_challenge_method: 'S256',
state: opts.state,
})
if (opts.loginHint) params.set('login_hint', opts.loginHint)
if (opts.primary) params.set('primary', opts.primary)
if (opts.dark !== undefined) params.set('dark', String(opts.dark))
return `${realmBase()}/protocol/openid-connect/auth?${params.toString()}`
}
export async function exchangeCodeForTokens(opts: {
code: string
codeVerifier: string
redirectUri: string
}): Promise<TokenResponse> {
const cfg = useRuntimeConfig()
const body = new URLSearchParams({
grant_type: 'authorization_code',
code: opts.code,
client_id: cfg.keycloakClientId,
client_secret: cfg.keycloakClientSecret,
redirect_uri: opts.redirectUri,
code_verifier: opts.codeVerifier,
})
return await $fetch<TokenResponse>(
`${realmBase()}/protocol/openid-connect/token`,
{
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: body.toString(),
},
)
}
export async function refreshTokens(refreshToken: string): Promise<TokenResponse> {
const cfg = useRuntimeConfig()
const body = new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: refreshToken,
client_id: cfg.keycloakClientId,
client_secret: cfg.keycloakClientSecret,
})
return await $fetch<TokenResponse>(
`${realmBase()}/protocol/openid-connect/token`,
{
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: body.toString(),
},
)
}
export function buildLogoutUrl(opts: {
idTokenHint: string
postLogoutRedirectUri: string
}): string {
const params = new URLSearchParams({
id_token_hint: opts.idTokenHint,
post_logout_redirect_uri: opts.postLogoutRedirectUri,
})
return `${realmBase()}/protocol/openid-connect/logout?${params.toString()}`
}
/**
* Decodifica payload de JWT sem validar assinatura — uso server-only para
* extrair claims do id_token/access_token recém-emitido pelo Keycloak.
* NÃO USAR para validar tokens recebidos de terceiros.
*/
export function decodeJwtPayload(token: string): Record<string, unknown> {
const [, payload] = token.split('.')
if (!payload) return {}
try {
const json = Buffer.from(payload, 'base64url').toString('utf-8')
return JSON.parse(json) as Record<string, unknown>
} catch {
return {}
}
}