'use strict' ; import { Config } from '@Base/config' ; import crypto from 'crypto' ; import { AccountEntity } from '@Entities/AccountEntity' ; import { accountFields , CheckAccountFields } from '@Entities/AccountFields' ; import { Domains } from '@Entities/Domains' ; import { Roles } from '@Entities/Sets/Roles' ; import { Places } from '@Entities/Places' ; import { Tokens } from '@Entities/Tokens' ; import { AuthToken } from '@Entities/AuthToken' ; import { CriteriaFilter } from '@Entities/EntityFilters/CriteriaFilter' ; import { GenericFilter } from '@Entities/EntityFilters/GenericFilter' ; import { ValidateResponse } from '@Route-Tools/EntityFieldDefn' ; import { getEntityField , setEntityField , getEntityUpdateForField } from '@Route-Tools/GetterSetter' ; import { createObject , getObject , getObjects , updateObjectFields , deleteOne , noCaseCollation , countObjects } from '@Tools/Db' ; import { GenUUID , genRandomString , IsNullOrEmpty , IsNotNullOrEmpty , SendVerificationEmail } from '@Tools/Misc' ; import { VKeyedCollection , SArray } from '@Tools/vTypes' ; import { Logger } from '@Tools/Logging' ; import { Requests } from './Requests' ; export let accountCollection = 'accounts' ; export function initAccounts ( ) { CheckAccountFields ( ) ; } ; export const Accounts = { async getAccountWithId ( pAccountId ) { return IsNullOrEmpty ( pAccountId ) ? null : getObject ( accountCollection , new GenericFilter ( { 'id' : pAccountId } ) ) ; } , async getAccountWithAuthToken ( pAuthToken ) { if ( IsNotNullOrEmpty ( pAuthToken ) ) { try { const tokenInfo = await Tokens . getTokenWithToken ( pAuthToken ) ; if ( IsNotNullOrEmpty ( tokenInfo ) ) { return Accounts . getAccountWithId ( tokenInfo . accountId ) ; } ; } catch ( err ) { throw err ; } ; } ; return null ; } , async getAccountWithUsername ( pUsername ) { if ( IsNotNullOrEmpty ( pUsername ) ) { return getObject ( accountCollection , new GenericFilter ( { 'username' : pUsername } ) , noCaseCollation ) ; } ; return null ; } , async getAccountWithNodeId ( pNodeId ) { return IsNullOrEmpty ( pNodeId ) ? null : getObject ( accountCollection , new GenericFilter ( { 'locationNodeid' : pNodeId } ) ) ; } , async getAccountWithEmail ( email ) { return IsNullOrEmpty ( email ) ? null : getObject ( accountCollection , new GenericFilter ( { 'email' : email } ) , noCaseCollation ) ; } , async addAccount ( pAccountEntity ) { Logger . info ( `Accounts: creating account ${ pAccountEntity . username }, id=${ pAccountEntity . id }` ) ; return createObject ( accountCollection , pAccountEntity ) ; } , async removeAccount ( pAccountEntity ) { Logger . info ( `Accounts: removing account ${ pAccountEntity . username }, id=${ pAccountEntity . id }` ) ; return deleteOne ( accountCollection , new GenericFilter ( { 'id' : pAccountEntity . id } ) ) ; } , async removeAccountContext ( pAccountEntity ) { Logger . info ( `Accounts: removing relationships for account ${ pAccountEntity . username }, id=${ pAccountEntity . id }` ) ; if ( pAccountEntity . connections ) { for ( const aConnectionName of pAccountEntity . connections ) { const aConnection = await Accounts . getAccountWithUsername ( aConnectionName ) ; if ( aConnection && aConnection . connections ) { SArray . remove ( aConnection . connections , pAccountEntity . username ) ; } ; } ; } ; if ( pAccountEntity . friends ) { for ( const aFriendName of pAccountEntity . friends ) { const aFriend = await Accounts . getAccountWithUsername ( aFriendName ) ; if ( aFriend && aFriend . friends ) { SArray . remove ( aFriend . friends , pAccountEntity . username ) ; } ; } ; } ; for await ( const aDomain of Domains . enumerateAsync ( new GenericFilter ( { 'sponsorAccountId' : pAccountEntity . id } ) ) ) { await Domains . removeDomain ( aDomain ) ; await Domains . removeDomainContext ( aDomain ) ; } ; await Places . removeMany ( new GenericFilter ( { 'accountId' : pAccountEntity . id } ) ) ; } , async updateEntityFields ( pEntity , pFields ) { return updateObjectFields ( accountCollection , new GenericFilter ( { 'id' : pEntity . id } ) , pFields ) ; } , async getField ( pAuthToken , pAccount , pField , pRequestingAccount ? ) { return getEntityField ( accountFields , pAuthToken , pAccount , pField , pRequestingAccount ) ; } , async setField ( pAuthToken , pAccount , pField , pVal , pRequestingAccount ? , pUpdates ? ) { return setEntityField ( accountFields , pAuthToken , pAccount , pField , pVal , pRequestingAccount , pUpdates ) ; } , getUpdateForField ( pAccount , pField , pExisting ? ) { return getEntityUpdateForField ( accountFields , pAccount , pField , pExisting ) ; } , async validateFieldValue ( pFieldName , pValue ) { const defn = accountFields [ pFieldName ] ; if ( defn ) { return await defn . validate ( defn , defn . request_field_name , pValue ) ; } ; return { 'valid' : false , 'reason' : 'Unknown field name' } ; } , async accountCount ( pCriteria ) { return countObjects ( accountCollection , pCriteria ) ; } , createAccount ( pUsername , pPassword , pEmail ) { const newAcct = new AccountEntity ( ) ; newAcct . id = GenUUID ( ) ; newAcct . username = pUsername ; newAcct . email = pEmail . toLowerCase ( ) ; newAcct . roles = [ Roles . USER ] ; newAcct . friends = [ ] ; newAcct . connections = [ ] ; newAcct . whenCreated = new Date ( ) ; Accounts . storePassword ( newAcct , pPassword ) ; return newAcct ; } , enableAccount ( pAccount ) { let accountIsActive = false ; if ( Config [ 'metaverse-server' ] [ 'enable-account-email-verification' ] ) { pAccount . accountEmailVerified = false ; const verifyCode = GenUUID ( ) ; const request = Requests . createEmailVerificationRequest ( pAccount . id , verifyCode ) ; void Requests . add ( request ) ; void SendVerificationEmail ( pAccount , verifyCode ) ; } else { Accounts . doEnableAccount ( pAccount ) ; accountIsActive = true ; } ; return accountIsActive ; } , doEnableAccount ( pAccount ) { if ( Config [ 'metaverse-server' ] [ 'enable-account-email-verification' ] ) { pAccount . accountEmailVerified = true ; const updates = { 'accountEmailVerified' : true } ; void Accounts . updateEntityFields ( pAccount , updates ) ; } else { delete pAccount . accountEmailVerified ; const updates = { 'accountEmailVerified' : null } ; void Accounts . updateEntityFields ( pAccount , updates ) ; } ; } , async * enumerateAsync ( pPager , pInfoer ? , pScoper ? ) { for await ( const acct of getObjects ( accountCollection , pPager , pInfoer , pScoper ) ) { yield acct ; } ; } , storePassword ( pEntity , pPassword ) { pEntity . passwordSalt = genRandomString ( 16 ) ; pEntity . passwordHash = Accounts . hashPassword ( pPassword , pEntity . passwordSalt ) ; } , async validatePassword ( pAcct , pPassword ) { return Accounts . hashPassword ( pPassword , pAcct . passwordSalt ) === pAcct . passwordHash ; } , hashPassword ( pPassword , pSalt ) { const hash = crypto . createHmac ( 'sha512' , pSalt ) ; hash . update ( pPassword ) ; const val = hash . digest ( 'hex' ) ; return val ; } , async makeAccountsFriends ( pRequestingAccount , pTargetAccount ) { if ( SArray . hasNoCase ( pRequestingAccount . connections , pTargetAccount . username ) ) { const adminToken = Tokens . createSpecialAdminToken ( ) ; const updates = { } ; await Accounts . setField ( adminToken , pRequestingAccount , 'friends' , { 'add' : pTargetAccount . username } , pRequestingAccount , updates ) ; await Accounts . updateEntityFields ( pRequestingAccount , updates ) ; } } , async makeAccountsConnected ( pRequestingAccount , pTargetAccount ) { const adminToken = Tokens . createSpecialAdminToken ( ) ; let updates = { } ; await Accounts . setField ( adminToken , pRequestingAccount , 'connections' , { 'add' : pTargetAccount . username } , pRequestingAccount , updates ) ; await Accounts . updateEntityFields ( pRequestingAccount , updates ) ; updates = { } ; await Accounts . setField ( adminToken , pTargetAccount , 'connections' , { 'add' : pRequestingAccount . username } , pTargetAccount , updates ) ; await Accounts . updateEntityFields ( pTargetAccount , updates ) ; } , async removeConnection ( pAccount , pConnectionName ) { let updates = { } ; const adminToken = Tokens . createSpecialAdminToken ( ) ; await Accounts . setField ( adminToken , pAccount , 'connections' , { 'remove' : pConnectionName } , pAccount , updates ) ; await Accounts . setField ( adminToken , pAccount , 'friends' , { 'remove' : pConnectionName } , pAccount , updates ) ; await Accounts . updateEntityFields ( pAccount , updates ) ; const otherAccount = await Accounts . getAccountWithUsername ( pConnectionName ) ; if ( otherAccount ) { updates = { } ; await Accounts . setField ( adminToken , otherAccount , 'connections' , { 'remove' : pAccount . username } , otherAccount , updates ) ; await Accounts . setField ( adminToken , otherAccount , 'friends' , { 'remove' : pAccount . username } , otherAccount , updates ) ; await Accounts . updateEntityFields ( otherAccount , updates ) ; } ; } , async removeFriend ( pAccount , pFriendName ) { let updates = { } ; const adminToken = Tokens . createSpecialAdminToken ( ) ; await Accounts . setField ( adminToken , pAccount , 'friends' , { 'remove' : pFriendName } , pAccount , updates ) ; await Accounts . updateEntityFields ( pAccount , updates ) ; const otherAccount = await Accounts . getAccountWithUsername ( pFriendName ) ; if ( otherAccount ) { updates = { } ; await Accounts . setField ( adminToken , otherAccount , 'friends' , { 'remove' : pAccount . username } , otherAccount , updates ) ; await Accounts . updateEntityFields ( otherAccount , updates ) ; } ; } , isOnline ( pAcct ) { if ( pAcct && pAcct . timeOfLastHeartbeat ) { return ( Date . now ( ) . valueOf ( ) - pAcct . timeOfLastHeartbeat . valueOf ( ) ) < ( Config [ "metaverse-server" ] [ "heartbeat-seconds-until-offline" ] * 1000 ) ; } ; return false ; } , dateWhenNotOnline ( ) { const whenOffline = new Date ( Date . now ( ) - ( Config [ "metaverse-server" ] [ "heartbeat-seconds-until-offline" ] * 1000 ) ) ; return whenOffline ; } , isAdmin ( pAcct ) { return SArray . has ( pAcct . roles , Roles . ADMIN ) ; } , isEnabled ( pAcct ) { return pAcct . accountEmailVerified ?? true ; } } ;