(token: string)
| 574 | }; |
| 575 | |
| 576 | const decodeJwtPayload = (token: string): Readonly<Record<string, unknown>> | null => { |
| 577 | const payload = token.split(".")[1]; |
| 578 | if (!payload) return null; |
| 579 | if (!/^[A-Za-z0-9_-]+$/.test(payload) || payload.length % 4 === 1) return null; |
| 580 | const base64 = payload.replaceAll("-", "+").replaceAll("_", "/"); |
| 581 | const padded = base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), "="); |
| 582 | // atob yields latin1 code units; JWT payloads are UTF-8 bytes, so re-decode |
| 583 | // them properly or non-ASCII claim values (accented emails, names) garble. |
| 584 | const utf8 = new TextDecoder().decode( |
| 585 | Uint8Array.from(globalThis.atob(padded), (char) => char.charCodeAt(0)), |
| 586 | ); |
| 587 | const decoded = decodeJwtClaims(utf8); |
| 588 | return Option.isSome(decoded) ? decoded.value : null; |
| 589 | }; |
| 590 | |
| 591 | export const idTokenIdentityLabel = (idToken: string | undefined): string | undefined => { |
| 592 | if (!idToken) return undefined; |
no test coverage detected