(token: string)
| 513 | }; |
| 514 | |
| 515 | const decodeJwtPayload = (token: string): Readonly<Record<string, unknown>> | null => { |
| 516 | const payload = token.split(".")[1]; |
| 517 | if (!payload) return null; |
| 518 | if (!/^[A-Za-z0-9_-]+$/.test(payload) || payload.length % 4 === 1) return null; |
| 519 | const base64 = payload.replaceAll("-", "+").replaceAll("_", "/"); |
| 520 | const padded = base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), "="); |
| 521 | // atob yields latin1 code units; JWT payloads are UTF-8 bytes, so re-decode |
| 522 | // them properly or non-ASCII claim values (accented emails, names) garble. |
| 523 | const utf8 = new TextDecoder().decode( |
| 524 | Uint8Array.from(globalThis.atob(padded), (char) => char.charCodeAt(0)), |
| 525 | ); |
| 526 | const decoded = decodeJwtClaims(utf8); |
| 527 | return Option.isSome(decoded) ? decoded.value : null; |
| 528 | }; |
| 529 | |
| 530 | export const idTokenIdentityLabel = (idToken: string | undefined): string | undefined => { |
| 531 | if (!idToken) return undefined; |
no test coverage detected