@nopwdio/sdk-js
Version:
Nopwd JS SDK
217 lines • 8.82 kB
JavaScript
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