import { SplitCookieWithUserName } from "./types" ; import { crypto_sign_verify_detached , from_base64 , crypto_generichash , crypto_generichash_BYTES , } from "libsodium-wrappers" ; import * as http from "http" ; import { ServerConfig } from "./server-config" ; const TIDDLY_SERVER_AUTH_COOKIE = "TiddlyServerAuth" ; const isoDateRegex = /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z$/ ; export const checkCookieAuth = ( request , settings ) => { if ( ! request . headers . cookie ) return false ; const cookies = { } ; const requestCookie = request . headers . cookie as string ; requestCookie . split ( ";" ) . forEach ( ( cookie ) => { const parts = cookie . split ( "=" ) ; if ( ! parts || ! Array . isArray ( parts ) || parts . length < 2 ) return ; const userName = parts . shift ( ) ?. trim ( ) || "" ; cookies [ userName ] = decodeURI ( parts . join ( "=" ) ) ; } ) ; let auth = cookies [ TIDDLY_SERVER_AUTH_COOKIE ] ; if ( ! auth ) return false ; let cookieData = parseAuthCookie ( auth , true ) ; if ( ! cookieData || cookieData . length !== 6 || ! cookieData [ 5 ] ) return false ; return validateCookie ( cookieData , false , settings ) ; } ; export function lookupAccount ( settings , hash , username ) { return Object . keys ( settings . authAccounts ) . find ( groupID => { let _user = settings . authAccounts [ groupID ] . clientKeys [ username ] ; if ( ! _user ) return false ; let _key = from_base64 ( _user . publicKey ) ; let _hash = crypto_generichash ( crypto_generichash_BYTES , _key , undefined , "base64" ) ; return hash === _hash ; } ) ; } export const validateCookie = ( cookieData , logRegisterNotice , settings ) => { const authCookieAge = settings ?. authCookieAge || 0 ; let [ username , type , timestamp , hash , sig , suffix ] = cookieData ; if ( type !== "key" ) return false ; const account = lookupAccount ( settings , hash , username ) ; if ( ! account ) { if ( logRegisterNotice ) console . log ( logRegisterNotice ) ; return false ; } try { let { publicKey , cookieSalt } = settings . authAccounts [ account ] . clientKeys [ username ] ; const valid = crypto_sign_verify_detached ( from_base64 ( sig ) , username + type + timestamp + hash , from_base64 ( publicKey ) ) && ( suffix === undefined || suffix === cookieSalt ) && isoDateRegex . test ( timestamp ) && Date . now ( ) - new Date ( timestamp ) . valueOf ( ) < authCookieAge * 1000 ; return valid ? [ account , username , cookieSalt ] : false ; } catch ( e ) { return false ; } } ; export const parseAuthCookie = ( cookie , suffix ) => { let splitCookie = cookie . split ( "|" ) ; const length = splitCookie . length ; const expectLength = suffix ? 6 : 5 ; if ( length > expectLength ) { const nameLength = splitCookie . length - expectLength - 1 ; const name = splitCookie . slice ( 0 , nameLength ) . join ( "|" ) ; const rest = splitCookie . slice ( nameLength ) ; return ( [ name , ... rest ] as unknown ) as SplitCookieWithUserName ; } else if ( length === expectLength ) { return ( splitCookie as unknown ) as SplitCookieWithUserName ; } else { return false ; } } ; export const getSetCookie = ( name , value , secure , age ) => { let flags = { Secure : secure , HttpOnly : true , "Max-Age" : age . toString ( ) , SameSite : "Strict" , Path : "/" , } ; return [ name + "=" + value , ... Object . keys ( flags ) . filter ( k => ! ! flags [ k ] ) . map ( k => k + ( typeof flags [ k ] === "string" ? "=" + flags [ k ] : "" ) ) , ] . join ( "; " ) ; } ;