1 | import { AuthenticationResult, PopupConfigOptions } from './global';
|
2 |
|
3 | import {
|
4 | DEFAULT_AUTHORIZE_TIMEOUT_IN_SECONDS,
|
5 | CLEANUP_IFRAME_TIMEOUT_IN_SECONDS
|
6 | } from './constants';
|
7 |
|
8 | import {
|
9 | PopupTimeoutError,
|
10 | TimeoutError,
|
11 | GenericError,
|
12 | PopupCancelledError
|
13 | } from './errors';
|
14 |
|
15 | export const parseAuthenticationResult = (
|
16 | queryString: string
|
17 | ): AuthenticationResult => {
|
18 | if (queryString.indexOf('#') > -1) {
|
19 | queryString = queryString.substring(0, queryString.indexOf('#'));
|
20 | }
|
21 |
|
22 | const searchParams = new URLSearchParams(queryString);
|
23 |
|
24 | return {
|
25 | state: searchParams.get('state')!,
|
26 | code: searchParams.get('code') || undefined,
|
27 | error: searchParams.get('error') || undefined,
|
28 | error_description: searchParams.get('error_description') || undefined
|
29 | };
|
30 | };
|
31 |
|
32 | export const runIframe = (
|
33 | authorizeUrl: string,
|
34 | eventOrigin: string,
|
35 | timeoutInSeconds: number = DEFAULT_AUTHORIZE_TIMEOUT_IN_SECONDS
|
36 | ) => {
|
37 | return new Promise<AuthenticationResult>((res, rej) => {
|
38 | const iframe = window.document.createElement('iframe');
|
39 |
|
40 | iframe.setAttribute('width', '0');
|
41 | iframe.setAttribute('height', '0');
|
42 | iframe.style.display = 'none';
|
43 |
|
44 | const removeIframe = () => {
|
45 | if (window.document.body.contains(iframe)) {
|
46 | window.document.body.removeChild(iframe);
|
47 | window.removeEventListener('message', iframeEventHandler, false);
|
48 | }
|
49 | };
|
50 |
|
51 | let iframeEventHandler: (e: MessageEvent) => void;
|
52 |
|
53 | const timeoutSetTimeoutId = setTimeout(() => {
|
54 | rej(new TimeoutError());
|
55 | removeIframe();
|
56 | }, timeoutInSeconds * 1000);
|
57 |
|
58 | iframeEventHandler = function (e: MessageEvent) {
|
59 | if (e.origin != eventOrigin) return;
|
60 | if (!e.data || e.data.type !== 'authorization_response') return;
|
61 |
|
62 | const eventSource = e.source;
|
63 |
|
64 | if (eventSource) {
|
65 | (eventSource as any).close();
|
66 | }
|
67 |
|
68 | e.data.response.error
|
69 | ? rej(GenericError.fromPayload(e.data.response))
|
70 | : res(e.data.response);
|
71 |
|
72 | clearTimeout(timeoutSetTimeoutId);
|
73 | window.removeEventListener('message', iframeEventHandler, false);
|
74 |
|
75 |
|
76 |
|
77 | setTimeout(removeIframe, CLEANUP_IFRAME_TIMEOUT_IN_SECONDS * 1000);
|
78 | };
|
79 |
|
80 | window.addEventListener('message', iframeEventHandler, false);
|
81 | window.document.body.appendChild(iframe);
|
82 | iframe.setAttribute('src', authorizeUrl);
|
83 | });
|
84 | };
|
85 |
|
86 | export const openPopup = (url: string) => {
|
87 | const width = 400;
|
88 | const height = 600;
|
89 | const left = window.screenX + (window.innerWidth - width) / 2;
|
90 | const top = window.screenY + (window.innerHeight - height) / 2;
|
91 |
|
92 | return window.open(
|
93 | url,
|
94 | 'auth0:authorize:popup',
|
95 | `left=${left},top=${top},width=${width},height=${height},resizable,scrollbars=yes,status=1`
|
96 | );
|
97 | };
|
98 |
|
99 | export const runPopup = (config: PopupConfigOptions) => {
|
100 | return new Promise<AuthenticationResult>((resolve, reject) => {
|
101 | let popupEventListener: (e: MessageEvent) => void;
|
102 |
|
103 |
|
104 | const popupTimer = setInterval(() => {
|
105 | if (config.popup && config.popup.closed) {
|
106 | clearInterval(popupTimer);
|
107 | clearTimeout(timeoutId);
|
108 | window.removeEventListener('message', popupEventListener, false);
|
109 | reject(new PopupCancelledError(config.popup));
|
110 | }
|
111 | }, 1000);
|
112 |
|
113 | const timeoutId = setTimeout(() => {
|
114 | clearInterval(popupTimer);
|
115 | reject(new PopupTimeoutError(config.popup));
|
116 | window.removeEventListener('message', popupEventListener, false);
|
117 | }, (config.timeoutInSeconds || DEFAULT_AUTHORIZE_TIMEOUT_IN_SECONDS) * 1000);
|
118 |
|
119 | popupEventListener = function (e: MessageEvent) {
|
120 | if (!e.data || e.data.type !== 'authorization_response') {
|
121 | return;
|
122 | }
|
123 |
|
124 | clearTimeout(timeoutId);
|
125 | clearInterval(popupTimer);
|
126 | window.removeEventListener('message', popupEventListener, false);
|
127 | config.popup.close();
|
128 |
|
129 | if (e.data.response.error) {
|
130 | return reject(GenericError.fromPayload(e.data.response));
|
131 | }
|
132 |
|
133 | resolve(e.data.response);
|
134 | };
|
135 |
|
136 | window.addEventListener('message', popupEventListener);
|
137 | });
|
138 | };
|
139 |
|
140 | export const getCrypto = () => {
|
141 | return window.crypto;
|
142 | };
|
143 |
|
144 | export const createRandomString = () => {
|
145 | const charset =
|
146 | '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_~.';
|
147 | let random = '';
|
148 | const randomValues = Array.from(
|
149 | getCrypto().getRandomValues(new Uint8Array(43))
|
150 | );
|
151 | randomValues.forEach(v => (random += charset[v % charset.length]));
|
152 | return random;
|
153 | };
|
154 |
|
155 | export const encode = (value: string) => btoa(value);
|
156 | export const decode = (value: string) => atob(value);
|
157 |
|
158 | const stripUndefined = (params: any) => {
|
159 | return Object.keys(params)
|
160 | .filter(k => typeof params[k] !== 'undefined')
|
161 | .reduce((acc, key) => ({ ...acc, [key]: params[key] }), {});
|
162 | };
|
163 |
|
164 | export const createQueryParams = ({ clientId: client_id, ...params }: any) => {
|
165 | return new URLSearchParams(
|
166 | stripUndefined({ client_id, ...params })
|
167 | ).toString();
|
168 | };
|
169 |
|
170 | export const sha256 = async (s: string) => {
|
171 | const digestOp: any = getCrypto().subtle.digest(
|
172 | { name: 'SHA-256' },
|
173 | new TextEncoder().encode(s)
|
174 | );
|
175 |
|
176 | return await digestOp;
|
177 | };
|
178 |
|
179 | const urlEncodeB64 = (input: string) => {
|
180 | const b64Chars: { [index: string]: string } = { '+': '-', '/': '_', '=': '' };
|
181 | return input.replace(/[+/=]/g, (m: string) => b64Chars[m]);
|
182 | };
|
183 |
|
184 |
|
185 | const decodeB64 = (input: string) =>
|
186 | decodeURIComponent(
|
187 | atob(input)
|
188 | .split('')
|
189 | .map(c => {
|
190 | return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
|
191 | })
|
192 | .join('')
|
193 | );
|
194 |
|
195 | export const urlDecodeB64 = (input: string) =>
|
196 | decodeB64(input.replace(/_/g, '/').replace(/-/g, '+'));
|
197 |
|
198 | export const bufferToBase64UrlEncoded = (input: number[] | Uint8Array) => {
|
199 | const ie11SafeInput = new Uint8Array(input);
|
200 | return urlEncodeB64(
|
201 | window.btoa(String.fromCharCode(...Array.from(ie11SafeInput)))
|
202 | );
|
203 | };
|
204 |
|
205 | export const validateCrypto = () => {
|
206 | if (!getCrypto()) {
|
207 | throw new Error(
|
208 | 'For security reasons, `window.crypto` is required to run `auth0-spa-js`.'
|
209 | );
|
210 | }
|
211 | if (typeof getCrypto().subtle === 'undefined') {
|
212 | throw new Error(`
|
213 | auth0-spa-js must run on a secure origin. See https://github.com/auth0/auth0-spa-js/blob/master/FAQ.md#why-do-i-get-auth0-spa-js-must-run-on-a-secure-origin for more information.
|
214 | `);
|
215 | }
|
216 | };
|
217 |
|
218 |
|
219 |
|
220 |
|
221 | export const getDomain = (domainUrl: string) => {
|
222 | if (!/^https?:\/\//.test(domainUrl)) {
|
223 | return `https://${domainUrl}`;
|
224 | }
|
225 |
|
226 | return domainUrl;
|
227 | };
|
228 |
|
229 |
|
230 |
|
231 |
|
232 | export const getTokenIssuer = (
|
233 | issuer: string | undefined,
|
234 | domainUrl: string
|
235 | ) => {
|
236 | if (issuer) {
|
237 | return issuer.startsWith('https://') ? issuer : `https://${issuer}/`;
|
238 | }
|
239 |
|
240 | return `${domainUrl}/`;
|
241 | };
|
242 |
|
243 | export const parseNumber = (value: any): number | undefined => {
|
244 | if (typeof value !== 'string') {
|
245 | return value;
|
246 | }
|
247 | return parseInt(value, 10) || undefined;
|
248 | };
|