All checks were successful
Dev Build & Deploy Portal / build-deploy (push) Successful in 2m58s
Integra débitos, pagamentos e guias emitidas com API via composables e modais de extrato. Simplifica filtros do portal ao escopo do contribuinte logado. Refatora emissão pública de certidão com modelos dinâmicos e contrato idModelo. Corrige status de taxas (2=Paga, 3=Cancelada) e melhorias no proxy BFF/Keycloak. Co-authored-by: Cursor <cursoragent@cursor.com>
116 lines
3.7 KiB
TypeScript
116 lines
3.7 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,
|
|
})
|
|
try {
|
|
return await $fetch<TokenResponse>(
|
|
`${realmBase()}/protocol/openid-connect/token`,
|
|
{
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
body: body.toString(),
|
|
},
|
|
)
|
|
} catch (err: unknown) {
|
|
const fetchErr = err as { data?: { error?: string; error_description?: string }; message?: string }
|
|
const kcError = fetchErr.data?.error
|
|
const kcDesc = fetchErr.data?.error_description
|
|
const detail = kcDesc ?? kcError ?? fetchErr.message ?? 'erro desconhecido'
|
|
throw new Error(`Keycloak token: ${detail}`)
|
|
}
|
|
}
|
|
|
|
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 {}
|
|
}
|
|
}
|