UNPKG

@nopwdio/sdk-js

Version:
217 lines 8.82 kB
var __awaiter = (this && this.__awaiter) || function (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()); }); }; import { endpoint } from "../internal/api/endpoint.js"; import { AbortError, NetworkError, NotFoundError, TooManyRequestsError, UnauthorizedError, } from "../internal/api/errors.js"; import { generateKey, sign } from "../internal/crypto/ecdsa.js"; import { bufferTo64Safe, decodeFromSafe64 } from "../internal/crypto/encoding.js"; import { Mutex } from "../internal/util/mutex.js"; import { deleteItem, getItem, open, putItem } from "../internal/util/store.js"; import { UnexpectedError } from "./errors.js"; import { getPayload } from "./token.js"; import { isWebauthnSupported } from "./webauthn.js"; // global variables let session = undefined; let sessionMutex = new Mutex(); let sessionStateListeners = []; let signalWhenSessionExpiredTimeoutId = undefined; /** * Create a session and replace the current one if exists. * @param token the access token generated by email or passkey authentication methods. * @param lifetime the session maximum duration in second. By default 24h. * @param idleTimeout the session inactivity timeout. * @returns the session object. * @throws {AbortError} when the authentication flow has been canceled (using signal) * @throws {UnexpectedError} when an unexpected error occured * @throws {MissingTokenError} if the token is not defined * @throws {NetworkError} when a connection error occured * @throws {InvalidTokenError} when the access token is malformed or expired */ export const create = function (token, lifetime, idleTimeout) { return __awaiter(this, void 0, void 0, function* () { try { const now = Math.round(Date.now() / 1000); const keyPair = yield generateKey(); const publicKey = yield crypto.subtle.exportKey("jwk", keyPair.publicKey); lifetime = lifetime ? lifetime : 24 * 60 * 60; idleTimeout = idleTimeout ? idleTimeout : lifetime; const { session_id, next_challenge, idle_timeout, expires_at, created_at, created_with, used_at, } = yield endpoint({ method: "POST", ressource: "/sessions", data: { public_key: publicKey, idle_timeout: idleTimeout, lifetime: lifetime, access_token: token, }, }); const db = yield getNopwdDb(); const sessionObject = { id: "current", session_id, next_challenge, private_key: keyPair.privateKey, created_at: now, created_with: created_with, expires_at: expires_at, idle_timeout: idle_timeout, }; yield putItem(db, "sessions", sessionObject); session = { token: token, token_payload: getPayload(token), created_at: created_at, created_with: created_with, expires_at: expires_at, idle_timeout: idle_timeout, suggest_passkeys: (yield isWebauthnSupported()) && !created_with.includes("webauthn"), }; setSessionState(session); return session; } catch (e) { if (e instanceof NetworkError || e instanceof TooManyRequestsError || e instanceof AbortError) { throw e; } throw new UnexpectedError(e); } }); }; export const get = function () { return __awaiter(this, void 0, void 0, function* () { yield sessionMutex.lock(); try { const now = Math.round(Date.now() / 1000); if (session && session.token_payload.exp > now + 60) { return session; } session = yield refreshSession(); setSessionState(session); return session; } catch (e) { if (e instanceof UnauthorizedError || e instanceof NotFoundError) { const db = yield getNopwdDb(); yield deleteItem(db, "sessions", "current"); setSessionState(null); return null; } throw e; } finally { sessionMutex.unlock(); } }); }; export const revoke = function () { return __awaiter(this, void 0, void 0, function* () { const db = yield getNopwdDb(); try { const currentSession = yield getItem(db, "sessions", "current"); if (!currentSession) { return; } yield endpoint({ method: "DELETE", ressource: `/sessions/${currentSession.session_id}`, }); } catch (e) { if (e instanceof NetworkError || e instanceof TooManyRequestsError) { throw e; } throw new UnexpectedError(e); } finally { yield deleteItem(db, "sessions", "current"); setSessionState(null); } }); }; const refreshSession = function () { return __awaiter(this, void 0, void 0, function* () { try { const now = Math.round(Date.now() / 1000); const db = yield getNopwdDb(); const storedSession = yield getItem(db, "sessions", "current"); if (!storedSession) { return null; } if (storedSession.expires_at < now) { const db = yield getNopwdDb(); yield deleteItem(db, "sessions", "current"); return null; } const challenge = decodeFromSafe64(storedSession.next_challenge); const signature = yield sign(challenge, storedSession.private_key); const { access_token, next_challenge } = yield endpoint({ method: "POST", ressource: `/sessions/${storedSession.session_id}/tokens`, data: { signature: bufferTo64Safe(signature), }, }); const payload = getPayload(access_token); storedSession.next_challenge = next_challenge; yield putItem(db, "sessions", storedSession); const session = { token: access_token, token_payload: payload, created_at: storedSession.created_at, created_with: storedSession.created_with, expires_at: storedSession.expires_at, idle_timeout: storedSession.idle_timeout, suggest_passkeys: (yield isWebauthnSupported()) && !storedSession.created_with.includes("webauthn"), }; return session; } catch (e) { if (e instanceof UnauthorizedError || e instanceof NotFoundError) { const db = yield getNopwdDb(); yield deleteItem(db, "sessions", "current"); setSessionState(null); return null; } throw e; } }); }; export const addSessionStateChanged = function (listener) { return __awaiter(this, void 0, void 0, function* () { sessionStateListeners.push(listener); listener(yield get()); }); }; export const removeSessionStateChanged = function (listener) { sessionStateListeners.some((value, index) => { if (listener === value) { sessionStateListeners.splice(index, 1); return true; } return false; }); }; const setSessionState = function (value) { session = value; clearTimeout(signalWhenSessionExpiredTimeoutId); if (session) { signalWhenSessionExpiredTimeoutId = window.setTimeout(() => { setSessionState(null); }, session.idle_timeout * 1000); } sessionStateListeners.forEach((listener) => { listener(value); }); }; const getNopwdDb = function () { return __awaiter(this, void 0, void 0, function* () { return open("nopwd", [{ name: "sessions", id: "id", auto: false }]); }); }; //# sourceMappingURL=session.js.map