UNPKG

@casual-simulation/aux-common

Version:
342 lines 14.3 kB
/* CasualOS is a set of web-based tools designed to facilitate the creation of real-time, multi-user, context-aware interactive experiences. * * Copyright (c) 2019-2025 Casual Simulation, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. */ import { fromBase64String, toBase64String } from '../utils'; import { sha256, hmac } from 'hash.js'; import { toByteArray } from 'base64-js'; import { formatV1ConnectionToken, parseV1ConnectionToken, } from '../common/ConnectionToken'; import { isRecordKey, parseRecordKey } from './RecordKeys'; /** * The default lifetime at which a session key should be refreshed. */ export const REFRESH_LIFETIME_MS = 1000 * 60 * 60 * 24 * 7; // 1 week /** * Formats the given user ID, session ID, session secret, and expiration time into a key that is used to authenticate a user to a particular session. * @param userId The ID of the user. * @param sessionId The ID of the session. * @param sessionSecret The secret for the session. * @param expireTimeMs The unix timestamp that the key expires at. */ export function formatV1SessionKey(userId, sessionId, sessionSecret, expireTimeMs) { return `vSK1.${toBase64String(userId)}.${toBase64String(sessionId)}.${toBase64String(sessionSecret)}.${toBase64String((expireTimeMs !== null && expireTimeMs !== void 0 ? expireTimeMs : Infinity).toString())}`; } /** * Parses the given session key into a user ID and session ID, and session secret array. * Returns null if the key cannot be parsed. * @param key The key to parse. */ export function parseSessionKey(key) { return parseV1SessionKey(key); } /** * Parses a version 1 session key into a user ID, session ID, session secret, and expiration time. * Returns null if the key cannot be parsed or if it is not a V1 key. * @param key The key to parse. */ export function parseV1SessionKey(key) { if (!key) { return null; } if (!key.startsWith('vSK1.')) { return null; } const withoutVersion = key.slice('vSK1.'.length); let periodAfterUserId = withoutVersion.indexOf('.'); if (periodAfterUserId < 0) { return null; } const userIdBase64 = withoutVersion.slice(0, periodAfterUserId); const sessionIdPlusPassword = withoutVersion.slice(periodAfterUserId + 1); if (userIdBase64.length <= 0 || sessionIdPlusPassword.length <= 0) { return null; } const periodAfterSessionId = sessionIdPlusPassword.indexOf('.'); if (periodAfterSessionId < 0) { return null; } const sessionIdBase64 = sessionIdPlusPassword.slice(0, periodAfterSessionId); const passwordPlusExpireTime = sessionIdPlusPassword.slice(periodAfterSessionId + 1); if (sessionIdBase64.length <= 0 || passwordPlusExpireTime.length <= 0) { return null; } const periodAfterPassword = passwordPlusExpireTime.indexOf('.'); if (periodAfterPassword < 0) { return null; } const passwordBase64 = passwordPlusExpireTime.slice(0, periodAfterPassword); const expireTimeBase64 = passwordPlusExpireTime.slice(periodAfterPassword + 1); if (passwordBase64.length <= 0 || expireTimeBase64.length <= 0) { return null; } try { const userId = fromBase64String(userIdBase64); const sessionId = fromBase64String(sessionIdBase64); const password = fromBase64String(passwordBase64); const expireTimeText = fromBase64String(expireTimeBase64); const expireTime = expireTimeText === 'Infinity' ? null : parseInt(expireTimeText); return [userId, sessionId, password, expireTime]; } catch (err) { return null; } } /** * Formats the given user ID, session ID, connection secret, and expiration time into a key that is used to generate connection tokens. * @param userId The ID of the user. * @param sessionId The ID of the session. * @param sessionSecret The secret for the connections. * @param expireTimeMs The unix timestamp that the key expires at. */ export function formatV1ConnectionKey(userId, sessionId, connectionSecret, expireTimeMs) { return `vCK1.${toBase64String(userId)}.${toBase64String(sessionId)}.${toBase64String(connectionSecret)}.${toBase64String((expireTimeMs !== null && expireTimeMs !== void 0 ? expireTimeMs : Infinity).toString())}`; } /** * Parses the given connection key into a user ID and session ID, and connection secret array. * Returns null if the key cannot be parsed. * @param key The key to parse. */ export function parseConnectionKey(key) { return parseV1ConnectionKey(key); } /** * Parses a version 1 session key into a user ID, session ID, session secret, and expiration time. * Returns null if the key cannot be parsed or if it is not a V1 key. * @param key The key to parse. */ export function parseV1ConnectionKey(key) { if (!key) { return null; } if (!key.startsWith('vCK1.')) { return null; } const withoutVersion = key.slice('vCK1.'.length); let periodAfterUserId = withoutVersion.indexOf('.'); if (periodAfterUserId < 0) { return null; } const userIdBase64 = withoutVersion.slice(0, periodAfterUserId); const sessionIdPlusPassword = withoutVersion.slice(periodAfterUserId + 1); if (userIdBase64.length <= 0 || sessionIdPlusPassword.length <= 0) { return null; } const periodAfterSessionId = sessionIdPlusPassword.indexOf('.'); if (periodAfterSessionId < 0) { return null; } const sessionIdBase64 = sessionIdPlusPassword.slice(0, periodAfterSessionId); const passwordPlusExpireTime = sessionIdPlusPassword.slice(periodAfterSessionId + 1); if (sessionIdBase64.length <= 0 || passwordPlusExpireTime.length <= 0) { return null; } const periodAfterPassword = passwordPlusExpireTime.indexOf('.'); if (periodAfterPassword < 0) { return null; } const passwordBase64 = passwordPlusExpireTime.slice(0, periodAfterPassword); const expireTimeBase64 = passwordPlusExpireTime.slice(periodAfterPassword + 1); if (passwordBase64.length <= 0 || expireTimeBase64.length <= 0) { return null; } try { const userId = fromBase64String(userIdBase64); const sessionId = fromBase64String(sessionIdBase64); const password = fromBase64String(passwordBase64); const expireTimeText = fromBase64String(expireTimeBase64); const expireTime = expireTimeText === 'Infinity' ? null : parseInt(expireTimeText); return [userId, sessionId, password, expireTime]; } catch (err) { return null; } } /** * Formats the given OpenAI Key into a string that is detectable as an OpenAI Key. * @param apiKey The API Key that should be formatted. */ export function formatV1OpenAiKey(apiKey) { return `vAI1.${toBase64String(apiKey)}`; } /** * Determines if the given string represents an OpenAI Key. * @param apiKey The API Key. */ export function isOpenAiKey(apiKey) { return typeof apiKey === 'string' && apiKey.startsWith(`vAI1.`); } /** * Parses the given OpenAI Key. * @param key The key that should be parsed. */ export function parseOpenAiKey(key) { if (!key || typeof key !== 'string') { return null; } if (!key.startsWith('vAI1.')) { return null; } const withoutVersion = key.slice('vAI1.'.length); return [fromBase64String(withoutVersion)]; } /** * Generates a new connection token from the given key, connection ID, and device ID. * * Returns null if the key cannot be parsed. * @param key The connection key that should be used to generate the token. * @param connectionId The connection ID. * @param deviceId The device ID. * @param inst The ID of the instance that the connection is for. */ export function generateV1ConnectionToken(key, connectionId, recordName, inst) { const parsed = parseConnectionKey(key); if (!parsed) { return null; } if (isRecordKey(recordName)) { const parsed = parseRecordKey(recordName); if (parsed) { recordName = parsed[0]; // Use the record name from the record key } } recordName = recordName !== null && recordName !== void 0 ? recordName : ''; const [userId, sessionId, connectionSecret, expireTimeMs] = parsed; const hashHex = v1ConnectionTokenHmac(connectionSecret, connectionId, recordName, inst); return formatV1ConnectionToken(userId, sessionId, connectionId, recordName, inst, hashHex); } /** * Calculates the SHA-256 HMAC of the given connection ID, record name, and inst using the given connection secret. * @param connectionSecret The connection secret. * @param connectionId The ID of the connection. * @param recordName The name of the record. * @param inst The inst. */ export function v1ConnectionTokenHmac(connectionSecret, connectionId, recordName, inst) { const hash = hmac(sha256, toByteArray(connectionSecret), 'hex'); hash.update(connectionId); hash.update(recordName); hash.update(inst); const hashHex = hash.digest('hex'); return hashHex; } /** * Validates whether the given connection token is valid and was generated from the given connection key. * @param connectionToken The connection token to validate. * @param connectionSecret The secret for the connection. */ export function verifyConnectionToken(connectionToken, connectionSecret) { if (!connectionToken || !connectionSecret) { return false; } try { const parsed = parseV1ConnectionToken(connectionToken); if (parsed) { const [userId, sessionId, connectionId, recordName, inst, hash] = parsed; const expectedHash = v1ConnectionTokenHmac(connectionSecret, connectionId, recordName, inst); return hash === expectedHash; } else { return false; } } catch (_a) { return false; } } /** * Determines whether the given role is a super user role. * @param role The role to check. */ export function isSuperUserRole(role) { return role === 'superUser' || role === 'system'; } /** * Determines wether the given role is suitable for a package reviewer. * @param role The role. */ export function isPackageReviewerRole(role) { return role === 'superUser' || role === 'moderator' || role === 'system'; } /** * Determines whether the given time has expired. * Can be used to determine wether a session keys, connection keys, etc. has expired. * * If the given time is null, then the key is considered to never expire. * * @param expirationMs The time that the key expires in miliseconds at since 1 January 1970 (Unix Epoch). * @param nowMs The current time in milliseconds since 1 January 1970 (Unix Epoch). */ export function isExpired(expirationMs, nowMs = Date.now()) { return nowMs >= (expirationMs !== null && expirationMs !== void 0 ? expirationMs : Infinity); } /** * Determines wether the given key will expire within the next REFRESH_LIFETIME_MS. * * Returns true if the key has expired or will expire within the next REFRESH_LIFETIME_MS. * Returns false if the key will not expire within the next REFRESH_LIFETIME_MS. * @param expirationMs The time that the key expires in miliseconds at since 1 January 1970 (Unix Epoch). * @param nowMs The current time in milliseconds since 1 January 1970 (Unix Epoch). */ export function willExpire(expirationMs, nowMs = Date.now()) { return isExpired(expirationMs, nowMs + REFRESH_LIFETIME_MS); } /** * Determines if a key with the given expiration time can expire. * @param expirationMs The time that the key expires at in miliseconds since 1 January 1970 (Unix Epoch). */ export function canExpire(expirationMs) { return expirationMs !== null && isFinite(expirationMs) && expirationMs >= 0; } /** * Gets the amount of time in miliseconds until a token with the given expiration time should be refreshed. * * Returns 0 or a negative number if the key has expired or will expire within the next week (REFRESH_LIFETIME_MS). * Returns a positive number if the key will not expire within the next week (REFRESH_LIFETIME_MS). * Returns infinity if the key will never expire. * * @param expirationMs The time that the token expires in miliseconds since the Unix Epoch (1 January 1970). * @param nowMs The current time in miliseconds since the Unix Epoch (1 January 1970). */ export function timeUntilRefresh(expirationMs, nowMs = Date.now()) { return timeUntilExpiration(expirationMs, nowMs + REFRESH_LIFETIME_MS); } /** * Gets the amount of time until a token with the given expiration time expires. * * Returns 0 or a negative number if the key has expired. * Returns infinity if the key will never expire. * Returns some other positive number if the key will expire in the future. * * @param expirationMs The time that the token expires in miliseconds since the Unix Epoch (1 January 1970). * @param nowMs The current time in miliseconds since the Unix Epoch (1 January 1970). */ export function timeUntilExpiration(expirationMs, nowMs = Date.now()) { return (expirationMs !== null && expirationMs !== void 0 ? expirationMs : Infinity) - nowMs; } /** * Gets the expiration time of the given session key. * @param key The session key. * @returns Returns the expiration time in miliseconds since the Unix Epoch (1 January 1970). Returns -1 if the key is invalid. */ export function getSessionKeyExpiration(key) { const parsed = parseSessionKey(key); if (!parsed) { return -1; } const expireTimeMs = parsed[3]; return expireTimeMs !== null && expireTimeMs !== void 0 ? expireTimeMs : Infinity; } //# sourceMappingURL=AuthUtils.js.map