UNPKG

7.11 kBPlain TextView Raw
1import { AuthenticationResult, PopupConfigOptions } from './global';
2
3import {
4 DEFAULT_AUTHORIZE_TIMEOUT_IN_SECONDS,
5 CLEANUP_IFRAME_TIMEOUT_IN_SECONDS
6} from './constants';
7
8import {
9 PopupTimeoutError,
10 TimeoutError,
11 GenericError,
12 PopupCancelledError
13} from './errors';
14
15export 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
32export 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 // Delay the removal of the iframe to prevent hanging loading status
76 // in Chrome: https://github.com/auth0/auth0-spa-js/issues/240
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
86export 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
99export const runPopup = (config: PopupConfigOptions) => {
100 return new Promise<AuthenticationResult>((resolve, reject) => {
101 let popupEventListener: (e: MessageEvent) => void;
102
103 // Check each second if the popup is closed triggering a PopupCancelledError
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
140export const getCrypto = () => {
141 return window.crypto;
142};
143
144export 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
155export const encode = (value: string) => btoa(value);
156export const decode = (value: string) => atob(value);
157
158const 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
164export const createQueryParams = ({ clientId: client_id, ...params }: any) => {
165 return new URLSearchParams(
166 stripUndefined({ client_id, ...params })
167 ).toString();
168};
169
170export 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
179const urlEncodeB64 = (input: string) => {
180 const b64Chars: { [index: string]: string } = { '+': '-', '/': '_', '=': '' };
181 return input.replace(/[+/=]/g, (m: string) => b64Chars[m]);
182};
183
184// https://stackoverflow.com/questions/30106476/
185const 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
195export const urlDecodeB64 = (input: string) =>
196 decodeB64(input.replace(/_/g, '/').replace(/-/g, '+'));
197
198export 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
205export 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 * @ignore
220 */
221export const getDomain = (domainUrl: string) => {
222 if (!/^https?:\/\//.test(domainUrl)) {
223 return `https://${domainUrl}`;
224 }
225
226 return domainUrl;
227};
228
229/**
230 * @ignore
231 */
232export 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
243export const parseNumber = (value: any): number | undefined => {
244 if (typeof value !== 'string') {
245 return value;
246 }
247 return parseInt(value, 10) || undefined;
248};