UNPKG

@first-line/firstline-react

Version:
385 lines (372 loc) 19 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('react')) : typeof define === 'function' && define.amd ? define(['exports', 'react'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.reactFirstline = {}, global.React)); })(this, (function (exports, React) { 'use strict'; /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ /* global Reflect, Promise */ function __awaiter(thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); } function __generator(thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } } async function sha256(plain) { const encoder = new TextEncoder(); const data = encoder.encode(plain); return window.crypto.subtle.digest("SHA-256", data); } function base64urlencode(a) { var str = ""; var bytes = new Uint8Array(a); var len = bytes.byteLength; for (var i = 0; i < len; i++) { str += String.fromCharCode(bytes[i]); } return btoa(str).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); } async function generateChallenge(code_verifier) { var hashed = await sha256(code_verifier); var base64encoded = base64urlencode(hashed); return base64encoded; } function stringQuery(object) { return Object.entries(object) .filter(([key, value]) => value && value !== "undefined" && value !== "null") .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`) .join("&"); } function randomToken(length) { const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_."; const randomIdxs = window.crypto.getRandomValues(new Uint8Array(length)); let token = ""; randomIdxs.forEach((idx) => (token += characters[idx % characters.length])); return token; } function parseJwt$1(token) { const base64Url = token.split(".")[1]; const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/"); const jsonPayload = decodeURIComponent(window .atob(base64) .split("") .map(function (c) { return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2); }) .join("")); return JSON.parse(jsonPayload); } class FirstlineClient { constructor(options) { this.options = options; this.serverUrl = options.domain.includes("localhost") ? `http://${options.domain}` : `https://${options.domain}`; } async loginRedirect(options) { var _a, _b, _c; const code_verifier = randomToken(43); const authorizeQueryParams = { grant_type: "authorization_code", response_type: "code", response_mode: "query", audience: this.options.audience, client_id: this.options.client_id, redirect_uri: (_a = options === null || options === void 0 ? void 0 : options.redirect_uri) !== null && _a !== void 0 ? _a : this.options.redirect_uri, state: (_b = options === null || options === void 0 ? void 0 : options.state) !== null && _b !== void 0 ? _b : randomToken(64), code_challenge: await generateChallenge(code_verifier), action_hint: (_c = options === null || options === void 0 ? void 0 : options.action_hint) !== null && _c !== void 0 ? _c : "login", }; const query = stringQuery(authorizeQueryParams); window.localStorage.setItem("state", authorizeQueryParams.state); window.localStorage.setItem("code_verifier", code_verifier); window.location["assign"](`${this.serverUrl}/api/v3/authorize?${query}`); } async verifyRedirect() { window.location["assign"](`${this.serverUrl}/ui/mail-confirmation?redirect_uri=${encodeURIComponent(this.options.redirect_uri)}`); } async exchangeAuthorizationCode(authorizationCode, code_verifier, state) { const exchangeTokenRequest = { grant_type: "authorization_code", code: authorizationCode, client_id: this.options.client_id, code_verifier: code_verifier, redirect_uri: this.options.redirect_uri, state: state, }; const exchangeTokenParams = stringQuery(exchangeTokenRequest); const token_response = await fetch(`${this.serverUrl}/api/v3/oauth/token?${exchangeTokenParams}`, { method: "post", }).then((response) => response.json()); return token_response; } async exchangeRefreshToken(refreshToken) { const exchangeTokenRequest = { grant_type: "refresh_token", refresh_token: refreshToken, client_id: this.options.client_id, }; const exchangeTokenParams = stringQuery(exchangeTokenRequest); const token_response = await fetch(`${this.serverUrl}/api/v3/oauth/token?${exchangeTokenParams}`, { method: "post", }).then((response) => response.json()); return token_response; } async logout() { var _a; window.localStorage.removeItem("refresh_token"); const logoutRequest = { client_id: this.options.client_id, logout_uri: (_a = this.options.logout_uri) !== null && _a !== void 0 ? _a : this.options.redirect_uri, }; const logoutParams = stringQuery(logoutRequest); window.location["assign"](`${this.serverUrl}/api/v3/logout?${logoutParams}`); } async doExchangeCode() { const params = new URLSearchParams(window.location.search); const codeParam = params.get("code"); const stateParam = params.get("state"); const storedState = window.localStorage.getItem("state"); const storedCodeVerifier = window.localStorage.getItem("code_verifier"); if (codeParam && stateParam && storedState && storedCodeVerifier) { if (stateParam === storedState) { const tokenResponse = await this.exchangeAuthorizationCode(codeParam, storedCodeVerifier, stateParam); window.localStorage.removeItem("state"); window.localStorage.removeItem("code_verifier"); window.localStorage.setItem("refresh_token", tokenResponse.refresh_token); window.history.pushState("object or string", "Title", "/" + window.location.href .substring(window.location.href.lastIndexOf("/") + 1) .split("?")[0]); return tokenResponse; } } return null; } async doRefresh() { const refresh_token = window.localStorage.getItem("refresh_token"); if (refresh_token) { const tokenResponse = await this.exchangeRefreshToken(refresh_token); if (tokenResponse.refresh_token) { window.localStorage.setItem("refresh_token", tokenResponse.refresh_token); return tokenResponse; } else { window.localStorage.removeItem("refresh_token"); } } return null; } async doExchangeOrRefresh() { let tokens = await this.doRefresh(); if (!tokens) tokens = await this.doExchangeCode(); return tokens; } getUser(tokens) { if (tokens === null || tokens === void 0 ? void 0 : tokens.id_token) { return parseJwt$1(tokens.id_token); } return null; } isEmailVerified(tokens) { const userObject = this.getUser(tokens); return userObject.is_verified ? true : false; } } var parseJwt = function (token) { try { return JSON.parse(atob(token.split(".")[1])); } catch (e) { return null; } }; var notWrapped = function () { throw new Error("You need to wrap your components with <FirstlineProvider>"); }; var initialFirstlineContext = { getTokens: notWrapped, getAccessToken: notWrapped, loginWithRedirect: notWrapped, verifyEmail: notWrapped, logout: notWrapped, doRefresh: notWrapped, doExchangeOrRefresh: notWrapped, isAuthenticated: false, isLoading: true, user: undefined, isEmailVerified: false, }; var FirstlineContext = React.createContext(initialFirstlineContext); var FirstlineProvider = function (options) { var client = React.useState(function () { return new FirstlineClient(options.clientOptions); })[0]; var _a = React.useState(true), loading = _a[0], setLoading = _a[1]; var _b = React.useState(undefined), tokens = _b[0], setTokens = _b[1]; var _c = React.useState(undefined), user = _c[0], setUser = _c[1]; var _d = React.useState(undefined), isEmailVerified_ = _d[0], setIsEmailVerified_ = _d[1]; var loginWithRedirect = React.useCallback(function (options) { return client.loginRedirect(options); }, [client]); var verifyEmail = React.useCallback(function () { return client.verifyRedirect(); }, [client]); var exchangeAuthorizationCode = React.useCallback(function (authorizationCode, code_verifier, state) { return client.exchangeAuthorizationCode(authorizationCode, code_verifier, state); }, [client]); var logout = React.useCallback(function () { return client.logout(); }, [client]); var doRefresh = React.useCallback(function () { return client.doRefresh(); }, [client]); var getUser = React.useCallback(function (tokens) { return client.getUser(tokens); }, [client]); var isEmailVerified = React.useCallback(function (tokens) { return client.isEmailVerified(tokens); }, [client]); var doExchangeOrRefresh = React.useCallback(function (initial) { if (initial === void 0) { initial = false; } return __awaiter(void 0, void 0, void 0, function () { var tokens_1; return __generator(this, function (_a) { switch (_a.label) { case 0: if (!(!loading || initial)) return [3 /*break*/, 2]; setLoading(true); return [4 /*yield*/, client.doExchangeOrRefresh()]; case 1: tokens_1 = _a.sent(); setTokens(tokens_1); return [2 /*return*/, tokens_1]; case 2: return [2 /*return*/]; } }); }); }, [tokens, client, loading, setLoading, setTokens]); var getTokens = React.useCallback(function () { return __awaiter(void 0, void 0, void 0, function () { var _a; return __generator(this, function (_b) { switch (_b.label) { case 0: if (!(tokens !== null && tokens !== void 0)) return [3 /*break*/, 1]; _a = tokens; return [3 /*break*/, 3]; case 1: return [4 /*yield*/, doExchangeOrRefresh()]; case 2: _a = (_b.sent()); _b.label = 3; case 3: return [2 /*return*/, _a]; } }); }); }, [tokens, doExchangeOrRefresh]); var getAccessToken = React.useCallback(function (check_expiry) { if (check_expiry === void 0) { check_expiry = true; } return __awaiter(void 0, void 0, void 0, function () { var decodedJwt, tokens_2; return __generator(this, function (_a) { switch (_a.label) { case 0: if ((tokens === null || tokens === void 0 ? void 0 : tokens.access_token) && check_expiry) { decodedJwt = parseJwt(tokens.access_token); if (decodedJwt.exp * 1000 >= Date.now()) return [2 /*return*/, tokens.access_token]; } if (!tokens) return [3 /*break*/, 1]; return [2 /*return*/, tokens.access_token]; case 1: return [4 /*yield*/, doExchangeOrRefresh()]; case 2: tokens_2 = _a.sent(); return [2 /*return*/, tokens_2 === null || tokens_2 === void 0 ? void 0 : tokens_2.access_token]; } }); }); }, [tokens, doExchangeOrRefresh]); React.useEffect(function () { doExchangeOrRefresh(true).catch(function (e) { }); }, []); React.useEffect(function () { if (tokens === undefined) return; if (tokens === null || tokens === void 0 ? void 0 : tokens.id_token) { var userObject = getUser(tokens); setUser(userObject); var isEmailVerified_1 = isEmailVerified(tokens); setIsEmailVerified_(isEmailVerified_1); setLoading(false); } else { setLoading(false); } }, [tokens]); var firstlineContextValues = React.useMemo(function () { return { getTokens: getTokens, getAccessToken: getAccessToken, loginWithRedirect: loginWithRedirect, verifyEmail: verifyEmail, logout: logout, doRefresh: doRefresh, doExchangeOrRefresh: doExchangeOrRefresh, isAuthenticated: user ? true : false, isLoading: loading, user: user, isEmailVerified: isEmailVerified_, }; }, [ getTokens, getAccessToken, loginWithRedirect, exchangeAuthorizationCode, verifyEmail, logout, doRefresh, doExchangeOrRefresh, tokens, loading, user, isEmailVerified_, ]); return (React.createElement(FirstlineContext.Provider, { value: firstlineContextValues }, options.children)); }; var useFirstline = function () { return React.useContext(FirstlineContext); }; exports.FirstlineClient = FirstlineClient; exports.FirstlineProvider = FirstlineProvider; exports.useFirstline = useFirstline; })); //# sourceMappingURL=firstline-react.js.map