1 | const sha256 = async (str) => { |
2 | return await crypto.subtle.digest("SHA-256", new TextEncoder().encode(str)); |
3 | }; |
4 | |
5 | const generateNonce = () => [...crypto.getRandomValues(new Uint8Array(16))].map((v) => v.toString(16).padStart(2, "0")).join(""); |
6 | |
7 | const base64URLEncode = (string) => { |
8 | return btoa(String.fromCharCode.apply(null, new Uint8Array(string))) |
9 | .replace(/\+/g, "-") |
10 | .replace(/\//g, "_") |
11 | .replace(/=+$/, ""); |
12 | }; |
13 | |
14 | const base64URLDecode = (string) => { |
15 | return atob(string |
16 | .replace(/-/g, "+") |
17 | .replace(/_/g, "/") + |
18 | "=".repeat((4 - string.length % 4) % 4)); |
19 | }; |
20 | |
21 | export const redirectToLogin = async (cognitoLoginUrl, clientId) => { |
22 | const state = generateNonce(); |
23 | const codeVerifier = generateNonce(); |
24 | sessionStorage.setItem(`codeVerifier-${state}`, codeVerifier); |
25 | const codeChallenge = base64URLEncode(await sha256(codeVerifier)); |
26 | window.location = `${cognitoLoginUrl}/login?response_type=code&client_id=${clientId}&state=${state}&code_challenge_method=S256&code_challenge=${codeChallenge}&redirect_uri=${window.location.origin}`; |
27 | }; |
28 | |
29 | export const redirectToLogout = async (cognitoLoginUrl, clientId) => { |
30 | localStorage.removeItem("tokens"); |
31 | window.location = `${cognitoLoginUrl}/logout?client_id=${clientId}&logout_uri=${window.location.origin}`; |
32 | }; |
33 | |
34 | export const getValidTokens = async (cognitoLoginUrl, clientId, tokens) => { |
35 | const {exp} = JSON.parse(base64URLDecode(tokens.access_token.split(".")[1])); |
36 | |
37 | |
38 | if(new Date(exp * 1000).getTime() > new Date().getTime() + 5 * 60 * 1000) { |
39 | return tokens; |
40 | }else { |
41 | const res = await fetch(`${cognitoLoginUrl}/oauth2/token`, { |
42 | method: "POST", |
43 | headers: new Headers({"content-type": "application/x-www-form-urlencoded"}), |
44 | body: Object.entries({ |
45 | "grant_type": "refresh_token", |
46 | "client_id": clientId, |
47 | "redirect_uri": window.location.origin, |
48 | "refresh_token": tokens.refresh_token, |
49 | }).map(([k, v]) => `${k}=${v}`).join("&"), |
50 | }); |
51 | if (!res.ok) { |
52 | throw new Error(await res.json()); |
53 | } |
54 | const newTokens = { |
55 | ...tokens, |
56 | ...(await res.json()), |
57 | }; |
58 | localStorage.setItem("tokens", JSON.stringify(newTokens)); |
59 | return newTokens; |
60 | } |
61 | }; |
62 | |
63 | export const processLoginFlow = (cognitoLoginUrl, clientId) => async (init) => { |
64 | const searchParams = new URL(location).searchParams; |
65 | |
66 | if (searchParams.get("code") !== null) { |
67 | window.history.replaceState({}, document.title, "/"); |
68 | const state = searchParams.get("state"); |
69 | const codeVerifier = sessionStorage.getItem(`codeVerifier-${state}`); |
70 | sessionStorage.removeItem(`codeVerifier-${state}`); |
71 | if (codeVerifier === null) { |
72 | throw new Error("Unexpected code"); |
73 | } |
74 | const res = await fetch(`${cognitoLoginUrl}/oauth2/token`, { |
75 | method: "POST", |
76 | headers: new Headers({"content-type": "application/x-www-form-urlencoded"}), |
77 | body: Object.entries({ |
78 | "grant_type": "authorization_code", |
79 | "client_id": clientId, |
80 | "code": searchParams.get("code"), |
81 | "code_verifier": codeVerifier, |
82 | "redirect_uri": window.location.origin, |
83 | }).map(([k, v]) => `${k}=${v}`).join("&"), |
84 | }); |
85 | if (!res.ok) { |
86 | throw new Error(await res.json()); |
87 | } |
88 | const tokens = await res.json(); |
89 | localStorage.setItem("tokens", JSON.stringify(tokens)); |
90 | |
91 | init(tokens); |
92 | }else { |
93 | if (localStorage.getItem("tokens")) { |
94 | const tokens = JSON.parse(localStorage.getItem("tokens")); |
95 | init(tokens); |
96 | }else { |
97 | init(null); |
98 | } |
99 | } |
100 | }; |
101 | |