UNPKG

@open-condo/miniapp-utils

Version:

A set of helper functions / components / hooks used to build new condo apps fast

1 lines 25.7 kB
{"version":3,"sources":["../../src/helpers/oidc.ts","../../src/helpers/ip/utils.ts","../../src/helpers/urls.ts","../../src/helpers/uuid.ts"],"sourcesContent":["import { generators, Issuer } from 'openid-client'\nimport { z } from 'zod'\n\nimport { isSafeUrl } from './urls'\nimport { generateUUIDv4 } from './uuid'\n\nimport type { IncomingMessage, ServerResponse } from 'http'\nimport type { Client, UserinfoResponse } from 'openid-client'\n\ntype Session = Record<string, unknown> & {\n save: () => Promise<void>\n destroy: () => Promise<void>\n}\n\ntype SessionGetter = (req: IncomingMessage, res: ServerResponse) => Promise<Session>\ntype OIDCClientConfig = {\n serverUrl: string\n clientId: string\n clientSecret: string\n scope?: string\n clientOptions?: Record<string, unknown>\n issuerOptions?: Record<string, unknown>\n}\n\ntype LoggerType = {\n info: (data: unknown) => void\n error: (data: unknown) => void\n}\n\ntype OIDCCallbackData<UserInfo extends Record<string, unknown> = Record<string, never>> = {\n accessToken?: string\n refreshToken?: string\n idToken?: string\n userInfo?: UserinfoResponse<UserInfo>\n}\n\ntype ErrorHandler = (err: unknown, req: IncomingMessage, res: ServerResponse, next: NextFunction) => void\n\ninterface AbstractApp {\n get(endpoint: string, ...handlers: Array<RequestHandler | ErrorHandler>): void\n}\n\ntype MiddlewareOptions = {\n app: AbstractApp\n apiPrefix?: string\n}\n\ntype OnAuthSuccessHandler<UserInfo extends Record<string, unknown> = Record<string, never>> = (\n req: IncomingMessage,\n res: ServerResponse,\n data: OIDCCallbackData<UserInfo>\n) => void | Promise<void>\n\ntype OIDCMiddlewareOptions<UserInfo extends Record<string, unknown> = Record<string, never>> = {\n getSession: SessionGetter\n oidcConfig: OIDCClientConfig\n redirectUri: string | Array<string>\n onAuthSuccess?: OnAuthSuccessHandler<UserInfo>\n middlewareOptions?: MiddlewareOptions\n onError?: ErrorHandler\n logger?: LoggerType\n}\n\ntype NextFunction = (err?: unknown) => void\n\ntype RequestHandler = (req: IncomingMessage, res: ServerResponse, next?: NextFunction) => void | Promise<void>\n\nexport class OIDCMiddleware<UserInfo extends Record<string, unknown> = Record<string, never>> {\n private static OIDC_ID_TOKEN_KEY = 'oidcIdToken' as const\n private static OIDC_ACCESS_TOKEN_KEY = 'oidcAccessToken' as const\n private static OIDC_REFRESH_TOKEN_KEY = 'oidcRefreshToken' as const\n private static OIDC_NEXT_URL_KEY = 'oidcNextUrl' as const\n private static OIDC_CHECKS_KEY = 'oidcChecks' as const\n private static OIDC_REDIRECT_URI_KEY = 'oidcRedirectUri' as const\n private static CHECK_SCHEMA = z.object({ nonce: z.string(), state: z.string() })\n\n private readonly getSession: SessionGetter\n private readonly client: Client\n private readonly logger: LoggerType\n private readonly redirectUris: Array<string>\n private readonly onAuthSuccess?: OnAuthSuccessHandler<UserInfo>\n private readonly onError?: ErrorHandler\n private readonly middlewareOptions: MiddlewareOptions | undefined\n private readonly scope?: string\n\n static getQueryParams (req: IncomingMessage): URLSearchParams {\n return new URL(req.url || '/', 'https://_').searchParams\n }\n\n constructor ({\n getSession,\n oidcConfig,\n redirectUri,\n logger,\n onAuthSuccess,\n onError,\n middlewareOptions,\n }: OIDCMiddlewareOptions<UserInfo>) {\n this.getSession = getSession\n const { serverUrl, clientId, clientSecret, clientOptions, issuerOptions, scope } = oidcConfig\n\n const issuer = new Issuer({\n authorization_endpoint: `${serverUrl}/oidc/auth`,\n token_endpoint: `${serverUrl}/oidc/token`,\n end_session_endpoint: `${serverUrl}/oidc/session/end`,\n jwks_uri: `${serverUrl}/oidc/jwks`,\n revocation_endpoint: `${serverUrl}/oidc/token/revocation`,\n userinfo_endpoint: `${serverUrl}/oidc/me`,\n issuer: serverUrl,\n ...(issuerOptions || {}),\n })\n\n this.redirectUris = Array.isArray(redirectUri) ? redirectUri : [redirectUri]\n this.middlewareOptions = middlewareOptions\n this.onAuthSuccess = onAuthSuccess\n this.onError = onError\n this.scope = scope || 'openid'\n\n this.client = new issuer.Client({\n client_id: clientId,\n client_secret: clientSecret,\n redirect_uris: this.redirectUris,\n response_types: ['code'],\n token_endpoint_auth_method: 'client_secret_basic',\n ...(clientOptions || {}),\n })\n\n this.logger = logger || console\n\n this.sendError = this.sendError.bind(this)\n this.getAuthHandler = this.getAuthHandler.bind(this)\n this.getCallbackHandler = this.getCallbackHandler.bind(this)\n this.prepareMiddleware = this.prepareMiddleware.bind(this)\n }\n\n private sendError (err: unknown, req: IncomingMessage, res: ServerResponse, next?: NextFunction): void {\n if (next && this.onError) {\n this.onError(err, req, res, next)\n return\n }\n\n if (next) {\n next(err)\n return\n }\n\n const errId = generateUUIDv4()\n this.logger.error({ msg: 'oidc auth error', errId, err })\n\n res.writeHead(500, { 'Content-Type': 'text/plain' })\n res.end(`OIDC auth error: ${errId}`)\n }\n\n getAuthHandler (): RequestHandler {\n const sessionGetter = this.getSession\n const client = this.client\n const sendError = this.sendError\n const scope = this.scope\n const redirectUris = this.redirectUris\n\n return async function authHandler (req, res, next) {\n const session = await sessionGetter(req, res)\n\n try {\n const query = OIDCMiddleware.getQueryParams(req)\n const queryRedirectUri = query.get('redirect_uri')\n const next = query.get('next')\n\n if (next && isSafeUrl(next)) {\n session[OIDCMiddleware.OIDC_NEXT_URL_KEY] = next\n } else {\n delete session[OIDCMiddleware.OIDC_NEXT_URL_KEY]\n }\n\n const redirectUri = redirectUris.find(uri => uri === queryRedirectUri) ?? redirectUris[0]\n const checks = { nonce: generators.nonce(), state: generators.state() }\n session[OIDCMiddleware.OIDC_CHECKS_KEY] = { ...checks }\n session[OIDCMiddleware.OIDC_REDIRECT_URI_KEY] = redirectUri\n await session.save()\n\n const authUrl = client.authorizationUrl({\n scope,\n redirect_uri: redirectUri,\n ...checks,\n })\n\n res.writeHead(302, { Location: authUrl })\n res.end()\n } catch (err) {\n delete session[OIDCMiddleware.OIDC_CHECKS_KEY]\n delete session[OIDCMiddleware.OIDC_NEXT_URL_KEY]\n await session.save()\n\n return sendError(err, req, res, next)\n }\n }\n }\n\n getCallbackHandler (): RequestHandler {\n const sessionGetter = this.getSession\n const sendError = this.sendError\n const client = this.client\n const onAuthSuccess = this.onAuthSuccess\n const redirectUris = this.redirectUris\n\n return async function callbackHandler (req, res, next) {\n let session = await sessionGetter(req, res)\n\n try {\n const { success, data: checks } = OIDCMiddleware.CHECK_SCHEMA.safeParse(session[OIDCMiddleware.OIDC_CHECKS_KEY])\n const nextUrl = session[OIDCMiddleware.OIDC_NEXT_URL_KEY]\n const redirectUri = session[OIDCMiddleware.OIDC_REDIRECT_URI_KEY]\n\n if (typeof redirectUri !== 'string' || !redirectUris.includes(redirectUri)) {\n return sendError(new Error('Invalid redirect URI'), req, res, next)\n }\n\n if (!success) {\n return sendError(new Error('Invalid nonce or state'), req, res, next)\n }\n\n const params = client.callbackParams(req)\n const { access_token: accessToken, refresh_token: refreshToken, id_token: idToken } = await client.callback(redirectUri, params, checks)\n\n let userInfo: UserinfoResponse<UserInfo> | undefined\n if (accessToken) {\n userInfo = await client.userinfo<UserInfo>(accessToken)\n }\n\n // Step 1. Remove temporary session data and save session\n delete session[OIDCMiddleware.OIDC_CHECKS_KEY]\n delete session[OIDCMiddleware.OIDC_NEXT_URL_KEY]\n await session.save()\n\n // Step 2. Call onAuthSuccess to sync user, start new session and so on\n if (onAuthSuccess) {\n await onAuthSuccess(req, res, { accessToken, refreshToken, idToken, userInfo })\n // NOTE: session might be regenerated in onAuthSuccess\n session = await sessionGetter(req, res)\n }\n\n // Step 3. Save tokens for later use\n session[OIDCMiddleware.OIDC_ID_TOKEN_KEY] = idToken\n session[OIDCMiddleware.OIDC_ACCESS_TOKEN_KEY] = accessToken\n session[OIDCMiddleware.OIDC_REFRESH_TOKEN_KEY] = refreshToken\n\n await session.save()\n\n const location = typeof nextUrl === 'string' && isSafeUrl(nextUrl) ? nextUrl : '/'\n res.writeHead(302, { Location: location })\n res.end()\n } catch (err) {\n delete session[OIDCMiddleware.OIDC_CHECKS_KEY]\n delete session[OIDCMiddleware.OIDC_NEXT_URL_KEY]\n await session.save()\n\n return sendError(err, req, res, next)\n }\n }\n }\n\n prepareMiddleware (): AbstractApp | null {\n if (!this.middlewareOptions) {\n return null\n }\n\n const { app, apiPrefix = '/api/oidc' } = this.middlewareOptions\n\n app.get(`${apiPrefix}/auth`, this.getAuthHandler())\n app.get(`${apiPrefix}/callback`, this.getCallbackHandler())\n\n return app\n }\n}","// RegExp for testing if a string represents an IPv4 address\nconst v4Seg = '(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])'\nconst v4Str = `(${v4Seg}[.]){3}${v4Seg}`\nconst IPv4Reg = new RegExp(`^${v4Str}$`)\n\n// RegExp for testing if a string represents an IPv6 address\nconst v6Seg = '(?:[0-9a-fA-F]{1,4})'\nconst IPv6Reg = new RegExp(\n '^(' +\n `(?:${v6Seg}:){7}(?:${v6Seg}|:)|` +\n `(?:${v6Seg}:){6}(?:${v4Str}|:${v6Seg}|:)|` +\n `(?:${v6Seg}:){5}(?::${v4Str}|(:${v6Seg}){1,2}|:)|` +\n `(?:${v6Seg}:){4}(?:(:${v6Seg}){0,1}:${v4Str}|(:${v6Seg}){1,3}|:)|` +\n `(?:${v6Seg}:){3}(?:(:${v6Seg}){0,2}:${v4Str}|(:${v6Seg}){1,4}|:)|` +\n `(?:${v6Seg}:){2}(?:(:${v6Seg}){0,3}:${v4Str}|(:${v6Seg}){1,5}|:)|` +\n `(?:${v6Seg}:){1}(?:(:${v6Seg}){0,4}:${v4Str}|(:${v6Seg}){1,6}|:)|` +\n `(?::((?::${v6Seg}){0,5}:${v4Str}|(?::${v6Seg}){1,7}|:))` +\n ')(%[0-9a-zA-Z]{1,})?$'\n)\n\n/**\n * Returns true if the string represents an IPv4 address. Matches Node.js net.isIPv4\n * functionality.\n */\nexport function isIPv4 (s: string) {\n return IPv4Reg.test(s)\n}\n\n/**\n * Returns true if the string represents an IPv6 address. Matches Node.js net.isIPv6\n * functionality.\n */\nexport function isIPv6 (s: string) {\n return IPv6Reg.test(s)\n}\n\nexport function isIP (s: string) {\n if (isIPv4(s)) return 4\n if (isIPv6(s)) return 6\n return 0\n}\n","const REGEXP_ESCAPE_CHARS = /[\\\\^$.*+?()[\\]{}|]/g\nconst WILDCARD_REGEXP_PART = '([a-zA-Z0-9-]{1,63})'\nconst WILDCARD_REGEXP_PART_ESCAPED = _escapeRegexp(WILDCARD_REGEXP_PART)\n\ntype URLCache = Map<string, URL>\ntype RegExpCache = Map<string, RegExp>\n\nimport { isIP, isLocalhost, isSpecial } from './ip'\n\nexport function isSafeUrl (url: unknown): boolean {\n if (!url || typeof url !== 'string') return false\n \n let decodedUrl: string\n try {\n decodedUrl = decodeURI(url)\n } catch (error) {\n // If decodeURI fails, treat as unsafe\n return false\n }\n\n // ReDoS-safe approach: normalize the string first, then use simple regex\n // Remove all control characters and whitespace, then check for javascript:\n const normalizedUrl = decodedUrl\n // eslint-disable-next-line no-control-regex\n .replace(/[\\u0000-\\u001F\\s]/g, '') // Remove control chars and whitespace\n .toLowerCase()\n \n return !normalizedUrl.includes('javascript:')\n}\n\nexport function replaceDomainPrefix (originalUrl: string, prefix: string): string {\n const url = new URL(originalUrl)\n\n const originalHostnameParts = url.hostname.split('.')\n const suffixParts = originalHostnameParts.length > 2 ? originalHostnameParts.slice(1) : originalHostnameParts\n const suffix = suffixParts.join('.')\n\n url.hostname = `${prefix}.${suffix}`\n\n return url.toString()\n}\n\nfunction _escapeRegexp (source: string) {\n return source.replace(REGEXP_ESCAPE_CHARS, '\\\\$&')\n}\n\nexport type ReplaceDomainOptions = { encoded?: boolean, urlsCache?: URLCache, regexpsCache?: RegExpCache }\n\nfunction _getUrl (strUrl: string, cache?: URLCache): URL {\n if (!cache) return new URL(strUrl)\n\n let parsed = cache.get(strUrl)\n if (!parsed) {\n parsed = new URL(strUrl)\n cache.set(strUrl, parsed)\n }\n return parsed\n}\n\n/** NOTE: Don't use it outside of this file, since it mutates lastIndex */\nfunction _getCachedRegexp (pattern: string, cache?: RegExpCache): RegExp {\n // NOTE: no user input here\n // nosemgrep: javascript.lang.security.audit.detect-non-literal-regexp.detect-non-literal-regexp\n if (!cache) return new RegExp(pattern, 'g')\n\n let re = cache.get(pattern)\n if (!re) {\n // NOTE: no user input here\n // nosemgrep: javascript.lang.security.audit.detect-non-literal-regexp.detect-non-literal-regexp\n re = new RegExp(pattern, 'g')\n cache.set(pattern, re)\n }\n re.lastIndex = 0\n return re\n}\n\nexport function getWildcardDomain (domain: string, cache?: URLCache) {\n const url = _getUrl(domain, cache)\n const [prefix, ...rest] = url.hostname.split('.')\n const port = url.port ? `:${url.port}` : ''\n return {\n wildcardDomain: `${url.protocol}//*.${rest.join('.')}${port}`,\n prefix,\n }\n}\n\nexport function replaceDomain (source: string, from: string, to: string, options: ReplaceDomainOptions = {}): string {\n const { encoded = false, urlsCache, regexpsCache } = options\n\n const fromUrl = _getUrl(from, urlsCache)\n // NOTE: URL parser encodes * as %2A in some browsers (Chromium), so decode the hostname to check for wildcards\n const decodedHostname = decodeURIComponent(fromUrl.hostname)\n\n // NOTE: for non-wildcard domain just do simple replace\n if (!decodedHostname.startsWith('*.')) {\n // Add negative lookahead to ensure domain boundary\n // Not followed by: dot+domain chars, or domain chars directly\n const escapedFrom = _escapeRegexp(from)\n const fromWithBoundary = `${escapedFrom}(?!\\\\.${WILDCARD_REGEXP_PART}|${WILDCARD_REGEXP_PART})`\n const fromSearch = _getCachedRegexp(fromWithBoundary, regexpsCache)\n let replaced = source.replace(fromSearch, to)\n\n if (encoded) {\n const fromEncoded = encodeURIComponent(from)\n const escapedFromEncoded = _escapeRegexp(fromEncoded)\n // For encoded: not followed by %2E+domain chars or domain chars directly\n const fromEncodedWithBoundary = `${escapedFromEncoded}(?!%2E${WILDCARD_REGEXP_PART}|${WILDCARD_REGEXP_PART})`\n const fromEncodedSearch = _getCachedRegexp(fromEncodedWithBoundary, regexpsCache)\n replaced = replaced.replace(fromEncodedSearch, encodeURIComponent(to))\n }\n\n return replaced\n }\n\n // NOTE: need to apply some magic on wildcards\n // NOTE: only first * is needed\n // nosemgrep: javascript.lang.security.audit.incomplete-sanitization.incomplete-sanitization\n const fromPattern = from.replace('*', WILDCARD_REGEXP_PART)\n // NOTE: only first * is needed\n // nosemgrep: javascript.lang.security.audit.incomplete-sanitization.incomplete-sanitization\n const toPattern = to.replace('*', '$1')\n\n const escapedFrom = _escapeRegexp(fromPattern).replace(WILDCARD_REGEXP_PART_ESCAPED, WILDCARD_REGEXP_PART)\n // Add negative lookahead to ensure domain boundary\n const fromWithBoundary = `${escapedFrom}(?!\\\\.${WILDCARD_REGEXP_PART}|${WILDCARD_REGEXP_PART})`\n const fromSearch = _getCachedRegexp(fromWithBoundary, regexpsCache)\n\n let replaced = source.replace(fromSearch, toPattern)\n\n if (encoded) {\n // NOTE: only first * is needed\n // nosemgrep: javascript.lang.security.audit.incomplete-sanitization.incomplete-sanitization\n const fromEncoded = encodeURIComponent(from).replace('*', WILDCARD_REGEXP_PART)\n // NOTE: only first * is needed\n // nosemgrep: javascript.lang.security.audit.incomplete-sanitization.incomplete-sanitization\n const toEncoded = encodeURIComponent(to).replace('*', '$1')\n\n const escapedFromEncoded = _escapeRegexp(fromEncoded).replace(WILDCARD_REGEXP_PART_ESCAPED, WILDCARD_REGEXP_PART)\n // For encoded: not followed by %2E+domain chars or domain chars directly\n const fromEncodedWithBoundary = `${escapedFromEncoded}(?!%2E${WILDCARD_REGEXP_PART}|${WILDCARD_REGEXP_PART})` \n const fromEncodedSearch = _getCachedRegexp(fromEncodedWithBoundary, regexpsCache)\n\n replaced = replaced.replace(fromEncodedSearch, toEncoded)\n }\n\n return replaced\n}\n\ntype URLWithMeta = Readonly<URL> & {\n isIP: boolean\n isSpecialIP: boolean\n isLocalhost: boolean\n}\n\nexport function getUrlMeta (url: string): URLWithMeta | null {\n try {\n const u: URLWithMeta = new URL(url) as URLWithMeta\n const hostnameUnWrapped = (u.hostname.startsWith('[') && u.hostname.endsWith(']'))\n ? u.hostname.slice(1, -1)\n : u.hostname\n u.isIP = isIP(hostnameUnWrapped) > 0\n u.isSpecialIP = u.isIP && isSpecial(hostnameUnWrapped)\n u.isLocalhost = u.hostname === 'localhost' || (u.isIP && isLocalhost(hostnameUnWrapped))\n\n return u\n } catch {\n return null\n }\n}\n","import { randomBytes } from 'crypto'\n\n/**\n * Generates v4 UUIDs in both browser and Node environments\n * @example\n * const uuid = generateUUIDv4()\n */\nexport function generateUUIDv4 (): string {\n let randomValues: Uint8Array\n\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n // Browser or Node.js (if Node 19+ supports crypto.randomUUID)\n return crypto.randomUUID()\n } else if (typeof window !== 'undefined' && window.crypto && window.crypto.getRandomValues) {\n // Browser environment\n randomValues = new Uint8Array(16)\n window.crypto.getRandomValues(randomValues)\n } else {\n // Node.js environment\n randomValues = randomBytes(16)\n }\n\n // Setting the version (4) and variant (RFC4122)\n randomValues[6] = (randomValues[6] & 0x0f) | 0x40 // version 4\n randomValues[8] = (randomValues[8] & 0x3f) | 0x80 // variant\n\n return [...randomValues]\n .map((value, index) => {\n const hex = value.toString(16).padStart(2, '0')\n if (index === 4 || index === 6 || index === 8 || index === 10) {\n return `-${hex}`\n }\n return hex\n })\n .join('')\n}\n"],"mappings":"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","names":["next"]}