(token: string)
| 1018 | }; |
| 1019 | |
| 1020 | const decodeJwtPayload = (token: string): Readonly<Record<string, unknown>> | null => { |
| 1021 | const payload = token.split(".")[1]; |
| 1022 | if (!payload) return null; |
| 1023 | if (!/^[A-Za-z0-9_-]+$/.test(payload) || payload.length % 4 === 1) return null; |
| 1024 | const base64 = payload.replaceAll("-", "+").replaceAll("_", "/"); |
| 1025 | const padded = base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), "="); |
| 1026 | // atob yields latin1 code units; JWT payloads are UTF-8 bytes, so re-decode |
| 1027 | // them properly or non-ASCII claim values (accented emails, names) garble. |
| 1028 | const utf8 = new TextDecoder().decode( |
| 1029 | Uint8Array.from(globalThis.atob(padded), (char) => char.charCodeAt(0)), |
| 1030 | ); |
| 1031 | const decoded = decodeJwtClaims(utf8); |
| 1032 | return Option.isSome(decoded) ? decoded.value : null; |
| 1033 | }; |
| 1034 | |
| 1035 | export const idTokenIdentityLabel = (idToken: string | undefined): string | undefined => { |
| 1036 | if (!idToken) return undefined; |
no test coverage detected