(token: string)
| 476 | }; |
| 477 | |
| 478 | const decodeJwtPayload = (token: string): Readonly<Record<string, unknown>> | null => { |
| 479 | const payload = token.split(".")[1]; |
| 480 | if (!payload) return null; |
| 481 | if (!/^[A-Za-z0-9_-]+$/.test(payload) || payload.length % 4 === 1) return null; |
| 482 | const base64 = payload.replaceAll("-", "+").replaceAll("_", "/"); |
| 483 | const padded = base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), "="); |
| 484 | // atob yields latin1 code units; JWT payloads are UTF-8 bytes, so re-decode |
| 485 | // them properly or non-ASCII claim values (accented emails, names) garble. |
| 486 | const utf8 = new TextDecoder().decode( |
| 487 | Uint8Array.from(globalThis.atob(padded), (char) => char.charCodeAt(0)), |
| 488 | ); |
| 489 | const decoded = decodeJwtClaims(utf8); |
| 490 | return Option.isSome(decoded) ? decoded.value : null; |
| 491 | }; |
| 492 | |
| 493 | export const idTokenIdentityLabel = (idToken: string | undefined): string | undefined => { |
| 494 | if (!idToken) return undefined; |
no test coverage detected