2017-02-18 01:56:23 -07:00
'use strict' ;
2015-06-17 14:13:29 -04:00
2019-09-11 00:28:42 -04:00
const winston = require ( 'winston' ) ;
const passport = require ( 'passport' ) ;
const nconf = require ( 'nconf' ) ;
const validator = require ( 'validator' ) ;
const _ = require ( 'lodash' ) ;
const util = require ( 'util' ) ;
const db = require ( '../database' ) ;
const meta = require ( '../meta' ) ;
2021-02-22 11:16:43 -05:00
const analytics = require ( '../analytics' ) ;
2019-09-11 00:28:42 -04:00
const user = require ( '../user' ) ;
const plugins = require ( '../plugins' ) ;
const utils = require ( '../utils' ) ;
2020-10-11 21:49:37 -04:00
const slugify = require ( '../slugify' ) ;
2019-09-11 00:28:42 -04:00
const helpers = require ( './helpers' ) ;
const privileges = require ( '../privileges' ) ;
const sockets = require ( '../socket.io' ) ;
const authenticationController = module . exports ;
2015-06-27 21:26:19 -04:00
2019-09-11 02:02:07 -04:00
async function registerAndLoginUser ( req , res , userData ) {
2021-07-30 15:08:43 -04:00
if ( ! userData . hasOwnProperty ( 'email' ) ) {
2021-06-18 11:48:39 -04:00
userData . updateEmail = true ;
}
2020-11-20 16:06:26 -05:00
const data = await plugins . hooks . fire ( 'filter:register.interstitial' , {
2021-07-05 16:55:12 -04:00
req ,
2021-06-18 11:48:39 -04:00
userData ,
2019-09-11 02:02:07 -04:00
interstitials : [ ] ,
} ) ;
2017-07-20 08:51:04 -04:00
2019-09-11 02:02:07 -04:00
// If interstitials are found, save registration attempt into session and abort
const deferRegistration = data . interstitials . length ;
if ( deferRegistration ) {
userData . register = true ;
req . session . registration = userData ;
if ( req . body . noscript === 'true' ) {
2021-02-03 23:59:08 -07:00
res . redirect ( ` ${ nconf . get ( 'relative_path' ) } /register/complete ` ) ;
2019-09-11 02:02:07 -04:00
return ;
}
2021-02-03 23:59:08 -07:00
res . json ( { next : ` ${ nconf . get ( 'relative_path' ) } /register/complete ` } ) ;
2019-09-11 02:02:07 -04:00
return ;
}
const queue = await user . shouldQueueUser ( req . ip ) ;
2020-11-20 16:06:26 -05:00
const result = await plugins . hooks . fire ( 'filter:register.shouldQueue' , { req : req , res : res , userData : userData , queue : queue } ) ;
2019-09-11 02:02:07 -04:00
if ( result . queue ) {
return await addToApprovalQueue ( req , userData ) ;
}
const uid = await user . create ( userData ) ;
if ( res . locals . processLogin ) {
await authenticationController . doLogin ( req , uid ) ;
}
2021-07-18 20:06:26 +03:00
// Distinguish registrations through invites from direct ones
if ( userData . token ) {
// Token has to be verified at this point
await Promise . all ( [
user . confirmIfInviteEmailIsUsed ( userData . token , userData . email , uid ) ,
user . joinGroupsFromInvitation ( uid , userData . token ) ,
] ) ;
}
await user . deleteInvitationKey ( userData . email , userData . token ) ;
2021-02-03 23:59:08 -07:00
const next = req . session . returnTo || ` ${ nconf . get ( 'relative_path' ) } / ` ;
2021-02-05 11:05:21 -05:00
const complete = await plugins . hooks . fire ( 'filter:register.complete' , { uid : uid , next : next } ) ;
req . session . returnTo = complete . next ;
2019-11-19 12:02:14 -05:00
return complete ;
2015-06-27 21:26:19 -04:00
}
2015-06-17 14:13:29 -04:00
2019-09-11 00:28:42 -04:00
authenticationController . register = async function ( req , res ) {
const registrationType = meta . config . registrationType || 'normal' ;
if ( registrationType === 'disabled' ) {
return res . sendStatus ( 403 ) ;
}
2019-09-11 02:02:07 -04:00
const userData = req . body ;
2019-09-11 00:28:42 -04:00
try {
2020-11-16 22:47:23 +03:00
if ( userData . token || registrationType === 'invite-only' || registrationType === 'admin-invite-only' ) {
2019-09-11 00:28:42 -04:00
await user . verifyInvitation ( userData ) ;
}
2021-02-04 02:07:29 -07:00
if (
! userData . username ||
userData . username . length < meta . config . minimumUsernameLength ||
slugify ( userData . username ) . length < meta . config . minimumUsernameLength
) {
2019-09-11 00:28:42 -04:00
throw new Error ( '[[error:username-too-short]]' ) ;
}
if ( userData . username . length > meta . config . maximumUsernameLength ) {
throw new Error ( '[[error:username-too-long]]' ) ;
}
if ( userData . password !== userData [ 'password-confirm' ] ) {
throw new Error ( '[[user:change_password_error_match]]' ) ;
}
2020-11-06 08:40:00 -05:00
if ( userData . password . length > 512 ) {
2020-11-03 09:53:49 -05:00
throw new Error ( '[[error:password-too-long]]' ) ;
}
2019-09-11 00:28:42 -04:00
user . isPasswordValid ( userData . password ) ;
2021-11-18 16:42:18 -05:00
res . locals . processLogin = true ; // set it to false in plugin if you wish to just register only
2020-11-20 16:06:26 -05:00
await plugins . hooks . fire ( 'filter:register.check' , { req : req , res : res , userData : userData } ) ;
2019-09-11 00:28:42 -04:00
2019-09-11 02:02:07 -04:00
const data = await registerAndLoginUser ( req , res , userData ) ;
if ( data ) {
if ( data . uid && req . body . userLang ) {
await user . setSetting ( data . uid , 'userLang' , req . body . userLang ) ;
}
res . json ( data ) ;
2019-09-11 00:28:42 -04:00
}
} catch ( err ) {
helpers . noScriptErrors ( req , res , err . message , 400 ) ;
}
} ;
2019-09-11 02:02:07 -04:00
async function addToApprovalQueue ( req , userData ) {
userData . ip = req . ip ;
await user . addToApprovalQueue ( userData ) ;
2020-11-13 04:23:07 +01:00
let message = '[[register:registration-added-to-queue]]' ;
if ( meta . config . showAverageApprovalTime ) {
const average _time = await db . getObjectField ( 'registration:queue:approval:times' , 'average' ) ;
2020-11-12 22:23:50 -05:00
if ( average _time > 0 ) {
2021-10-05 10:13:24 -04:00
message += ` [[register:registration-queue-average-time, ${ Math . floor ( average _time / 60 ) } , ${ Math . floor ( average _time % 60 ) } ]] ` ;
2020-11-12 22:23:50 -05:00
}
2020-11-13 04:23:07 +01:00
}
if ( meta . config . autoApproveTime > 0 ) {
message += ` [[register:registration-queue-auto-approve-time, ${ meta . config . autoApproveTime } ]] ` ;
}
return { message : message } ;
2015-06-27 21:26:19 -04:00
}
2015-06-17 14:13:29 -04:00
2021-09-03 15:30:05 -04:00
authenticationController . registerComplete = async function ( req , res ) {
try {
// For the interstitials that respond, execute the callback with the form body
const data = await plugins . hooks . fire ( 'filter:register.interstitial' , {
req ,
userData : req . session . registration ,
interstitials : [ ] ,
} ) ;
2016-08-16 19:46:59 +02:00
2021-02-04 00:06:15 -07:00
const callbacks = data . interstitials . reduce ( ( memo , cur ) => {
2016-06-22 14:40:34 -04:00
if ( cur . hasOwnProperty ( 'callback' ) && typeof cur . callback === 'function' ) {
2019-04-15 12:33:57 -04:00
req . body . files = req . files ;
2021-02-26 09:58:48 -05:00
if (
( cur . callback . constructor && cur . callback . constructor . name === 'AsyncFunction' ) ||
2021-11-18 16:42:18 -05:00
cur . callback . length === 2 // non-async function w/o callback
2021-02-26 09:58:48 -05:00
) {
memo . push ( cur . callback ) ;
} else {
memo . push ( util . promisify ( cur . callback ) ) ;
}
2016-06-22 14:40:34 -04:00
}
return memo ;
} , [ ] ) ;
2021-09-03 15:30:05 -04:00
const done = function ( data ) {
2016-06-22 16:47:24 -04:00
delete req . session . registration ;
2021-09-24 19:23:46 -04:00
const relative _path = nconf . get ( 'relative_path' ) ;
2021-09-03 15:30:05 -04:00
if ( data && data . message ) {
2021-09-24 19:23:46 -04:00
return res . redirect ( ` ${ relative _path } /?register= ${ encodeURIComponent ( data . message ) } ` ) ;
2017-12-05 13:18:37 -05:00
}
2021-01-22 09:58:29 -05:00
2016-06-22 16:47:24 -04:00
if ( req . session . returnTo ) {
2021-09-24 19:23:46 -04:00
res . redirect ( relative _path + req . session . returnTo . replace ( new RegExp ( ` ^ ${ relative _path } ` ) , '' ) ) ;
2016-06-22 16:47:24 -04:00
} else {
2021-09-24 19:23:46 -04:00
res . redirect ( ` ${ relative _path } / ` ) ;
2016-06-22 16:47:24 -04:00
}
2016-08-22 16:24:28 -04:00
} ;
2016-06-22 16:47:24 -04:00
2021-01-22 09:58:29 -05:00
const results = await Promise . allSettled ( callbacks . map ( async ( cb ) => {
await cb ( req . session . registration , req . body ) ;
} ) ) ;
2021-05-17 10:50:50 -04:00
const errors = results . map ( result => result . status === 'rejected' && result . reason && result . reason . message ) . filter ( Boolean ) ;
2021-01-22 09:58:29 -05:00
if ( errors . length ) {
req . flash ( 'errors' , errors ) ;
2021-02-03 23:59:08 -07:00
return res . redirect ( ` ${ nconf . get ( 'relative_path' ) } /register/complete ` ) ;
2021-01-22 09:58:29 -05:00
}
2016-06-22 14:40:34 -04:00
2021-01-22 09:58:29 -05:00
if ( req . session . registration . register === true ) {
res . locals . processLogin = true ;
2021-11-18 16:42:18 -05:00
req . body . noscript = 'true' ; // trigger full page load on error
2021-07-30 14:50:56 -04:00
const data = await registerAndLoginUser ( req , res , req . session . registration ) ;
if ( ! data ) {
2021-07-30 15:08:43 -04:00
return winston . warn ( '[register] Interstitial callbacks processed with no errors, but one or more interstitials remain. This is likely an issue with one of the interstitials not properly handling a null case or invalid value.' ) ;
2021-07-30 14:50:56 -04:00
}
2021-09-03 15:30:05 -04:00
done ( data ) ;
2021-01-22 09:58:29 -05:00
} else {
// Update user hash, clear registration data in session
const payload = req . session . registration ;
2021-02-06 14:10:15 -07:00
const { uid } = payload ;
2021-01-22 09:58:29 -05:00
delete payload . uid ;
delete payload . returnTo ;
Object . keys ( payload ) . forEach ( ( prop ) => {
if ( typeof payload [ prop ] === 'boolean' ) {
payload [ prop ] = payload [ prop ] ? 1 : 0 ;
}
} ) ;
await user . setUserFields ( uid , payload ) ;
done ( ) ;
}
2021-09-03 15:30:05 -04:00
} catch ( err ) {
delete req . session . registration ;
res . redirect ( ` ${ nconf . get ( 'relative_path' ) } /?register= ${ encodeURIComponent ( err . message ) } ` ) ;
}
2016-06-22 12:42:37 -04:00
} ;
2016-10-13 11:43:39 +02:00
authenticationController . registerAbort = function ( req , res ) {
2021-06-16 16:03:06 -04:00
if ( req . uid ) {
2021-07-28 15:16:08 -04:00
// Clear interstitial data and continue on...
2021-06-16 16:03:06 -04:00
delete req . session . registration ;
2021-09-26 13:06:29 +03:00
res . redirect ( nconf . get ( 'relative_path' ) + ( req . session . returnTo || '/' ) ) ;
2021-06-16 16:03:06 -04:00
} else {
// End the session and redirect to home
req . session . destroy ( ( ) => {
res . clearCookie ( nconf . get ( 'sessionKey' ) , meta . configs . cookie . get ( ) ) ;
res . redirect ( ` ${ nconf . get ( 'relative_path' ) } / ` ) ;
} ) ;
}
2016-06-22 16:47:24 -04:00
} ;
2021-01-29 16:59:57 -05:00
authenticationController . login = async ( req , res , next ) => {
let { strategy } = await plugins . hooks . fire ( 'filter:login.override' , { req , strategy : 'local' } ) ;
if ( ! passport . _strategy ( strategy ) ) {
winston . error ( ` [auth/override] Requested login strategy " ${ strategy } " not found, reverting back to local login strategy. ` ) ;
strategy = 'local' ;
}
2020-11-20 16:06:26 -05:00
if ( plugins . hooks . hasListeners ( 'action:auth.overrideLogin' ) ) {
2021-01-29 16:59:57 -05:00
return continueLogin ( strategy , req , res , next ) ;
2015-06-17 14:13:29 -04:00
}
2021-02-04 00:06:15 -07:00
const loginWith = meta . config . allowLoginWith || 'username-email' ;
2018-10-30 17:09:27 -04:00
req . body . username = req . body . username . trim ( ) ;
2021-08-05 12:52:07 -04:00
const errorHandler = res . locals . noScriptErrors || helpers . noScriptErrors ;
try {
await plugins . hooks . fire ( 'filter:login.check' , { req : req , res : res , userData : req . body } ) ;
} catch ( err ) {
return errorHandler ( req , res , err . message , 403 ) ;
}
try {
const isEmailLogin = loginWith . includes ( 'email' ) && req . body . username && utils . isEmailValid ( req . body . username ) ;
const isUsernameLogin = loginWith . includes ( 'username' ) && ! validator . isEmail ( req . body . username ) ;
if ( isEmailLogin ) {
const username = await user . getUsernameByEmail ( req . body . username ) ;
if ( username !== '[[global:guest]]' ) {
req . body . username = username ;
}
2019-10-02 00:21:48 +03:00
}
2021-08-05 12:52:07 -04:00
if ( isEmailLogin || isUsernameLogin ) {
2021-10-22 15:56:31 -04:00
continueLogin ( strategy , req , res , next ) ;
2019-10-02 00:21:48 +03:00
} else {
2021-08-05 12:52:07 -04:00
errorHandler ( req , res , ` [[error:wrong-login-type- ${ loginWith } ]] ` , 400 ) ;
2019-10-02 00:21:48 +03:00
}
2021-08-05 12:52:07 -04:00
} catch ( err ) {
return errorHandler ( req , res , err . message , 500 ) ;
}
2015-06-17 14:13:29 -04:00
} ;
2021-01-29 16:59:57 -05:00
function continueLogin ( strategy , req , res , next ) {
2021-02-04 00:01:39 -07:00
passport . authenticate ( strategy , async ( err , userData , info ) => {
2015-06-17 14:13:29 -04:00
if ( err ) {
2021-04-07 14:21:45 -04:00
plugins . hooks . fire ( 'action:login.continue' , { req , strategy , userData , error : err } ) ;
2021-09-21 17:04:17 -04:00
return helpers . noScriptErrors ( req , res , err . data || err . message , 403 ) ;
2015-06-17 14:13:29 -04:00
}
if ( ! userData ) {
2021-03-17 12:10:52 -04:00
if ( info instanceof Error ) {
info = info . message ;
} else if ( typeof info === 'object' ) {
2015-06-17 14:13:29 -04:00
info = '[[error:invalid-username-or-password]]' ;
}
2021-04-07 14:21:45 -04:00
plugins . hooks . fire ( 'action:login.continue' , { req , strategy , userData , error : new Error ( info ) } ) ;
2017-07-20 08:51:04 -04:00
return helpers . noScriptErrors ( req , res , info , 403 ) ;
2015-06-17 14:13:29 -04:00
}
// Alter user cookie depending on passed-in option
if ( req . body . remember === 'on' ) {
2021-03-03 16:04:36 -05:00
const duration = meta . getSessionTTLSeconds ( ) * 1000 ;
2015-06-17 14:13:29 -04:00
req . session . cookie . maxAge = duration ;
req . session . cookie . expires = new Date ( Date . now ( ) + duration ) ;
} else {
req . session . cookie . maxAge = false ;
req . session . cookie . expires = false ;
}
2021-04-07 14:21:45 -04:00
plugins . hooks . fire ( 'action:login.continue' , { req , strategy , userData , error : null } ) ;
2021-02-05 14:31:02 -05:00
2019-10-07 23:13:43 -04:00
if ( userData . passwordExpiry && userData . passwordExpiry < Date . now ( ) ) {
2021-02-03 23:59:08 -07:00
winston . verbose ( ` [auth] Triggering password reset for uid ${ userData . uid } due to password policy ` ) ;
2015-06-17 14:13:29 -04:00
req . session . passwordExpired = true ;
2019-03-20 16:30:33 -04:00
2020-05-27 12:15:02 -04:00
const code = await user . reset . generate ( userData . uid ) ;
2021-10-22 15:56:31 -04:00
( res . locals . redirectAfterLogin || redirectAfterLogin ) ( req , res , ` ${ nconf . get ( 'relative_path' ) } /reset/ ${ code } ` ) ;
2015-06-17 14:13:29 -04:00
} else {
2018-12-07 11:29:20 -05:00
delete req . query . lang ;
2020-05-27 12:15:02 -04:00
await authenticationController . doLogin ( req , userData . uid ) ;
2021-02-04 00:06:15 -07:00
let destination ;
2020-05-27 12:15:02 -04:00
if ( req . session . returnTo ) {
2020-07-27 22:28:07 -04:00
destination = req . session . returnTo . startsWith ( 'http' ) ?
req . session . returnTo :
nconf . get ( 'relative_path' ) + req . session . returnTo ;
2020-05-27 12:15:02 -04:00
delete req . session . returnTo ;
} else {
2021-02-03 23:59:08 -07:00
destination = ` ${ nconf . get ( 'relative_path' ) } / ` ;
2020-05-27 12:15:02 -04:00
}
2018-12-07 11:29:20 -05:00
2021-10-22 15:56:31 -04:00
( res . locals . redirectAfterLogin || redirectAfterLogin ) ( req , res , destination ) ;
2015-06-17 14:13:29 -04:00
}
} ) ( req , res , next ) ;
}
2021-10-22 15:56:31 -04:00
function redirectAfterLogin ( req , res , destination ) {
if ( req . body . noscript === 'true' ) {
res . redirect ( ` ${ destination } ?loggedin ` ) ;
} else {
res . status ( 200 ) . send ( {
next : destination ,
} ) ;
}
}
2019-09-11 02:02:07 -04:00
authenticationController . doLogin = async function ( req , uid ) {
2016-03-08 12:17:12 +02:00
if ( ! uid ) {
2019-09-11 02:02:07 -04:00
return ;
2016-03-08 12:17:12 +02:00
}
2019-09-11 02:02:07 -04:00
const loginAsync = util . promisify ( req . login ) . bind ( req ) ;
2021-04-13 21:32:16 -04:00
2021-06-04 11:34:49 -04:00
const { reroll } = req . res . locals ;
if ( reroll !== false ) {
const regenerateSession = util . promisify ( req . session . regenerate ) . bind ( req . session ) ;
const sessionData = { ... req . session } ;
await regenerateSession ( ) ;
for ( const [ prop , value ] of Object . entries ( sessionData ) ) {
req . session [ prop ] = value ;
}
2021-04-14 16:13:02 -04:00
}
2019-09-11 02:02:07 -04:00
await loginAsync ( { uid : uid } ) ;
await authenticationController . onSuccessfulLogin ( req , uid ) ;
2016-03-08 12:29:19 +02:00
} ;
2016-02-26 16:45:44 +02:00
2019-09-11 02:02:07 -04:00
authenticationController . onSuccessfulLogin = async function ( req , uid ) {
2020-01-29 12:47:48 -05:00
/ *
* Older code required that this method be called from within the SSO plugin .
* That behaviour is no longer required , onSuccessfulLogin is now automatically
* called in NodeBB core . However , if already called , return prematurely
* /
2020-02-03 11:04:20 -05:00
if ( req . loggedIn && ! req . session . forceLogin ) {
2019-03-13 12:38:30 -04:00
return true ;
}
2019-09-11 02:02:07 -04:00
try {
const uuid = utils . generateUUID ( ) ;
req . uid = uid ;
req . loggedIn = true ;
await meta . blacklist . test ( req . ip ) ;
await user . logIP ( uid , req . ip ) ;
2020-11-23 16:05:58 -05:00
await user . bans . unbanIfExpired ( [ uid ] ) ;
2021-06-14 11:50:32 -04:00
await user . reset . cleanByUid ( uid ) ;
2019-09-11 02:02:07 -04:00
req . session . meta = { } ;
delete req . session . forceLogin ;
// Associate IP used during login with user account
req . session . meta . ip = req . ip ;
// Associate metadata retrieved via user-agent
req . session . meta = _ . extend ( req . session . meta , {
uuid : uuid ,
datetime : Date . now ( ) ,
platform : req . useragent . platform ,
browser : req . useragent . browser ,
version : req . useragent . version ,
} ) ;
await Promise . all ( [
2021-11-18 16:42:18 -05:00
new Promise ( ( resolve ) => {
req . session . save ( resolve ) ;
} ) ,
2019-09-11 02:02:07 -04:00
user . auth . addSession ( uid , req . sessionID ) ,
user . updateLastOnlineTime ( uid ) ,
user . updateOnlineUsers ( uid ) ,
2021-02-22 11:16:43 -05:00
analytics . increment ( 'logins' ) ,
2021-02-22 11:38:26 -05:00
db . incrObjectFieldBy ( 'global' , 'loginCount' , 1 ) ,
2019-09-11 02:02:07 -04:00
] ) ;
2020-05-16 22:17:20 -04:00
if ( uid > 0 ) {
2021-02-03 23:59:08 -07:00
await db . setObjectField ( ` uid: ${ uid } :sessionUUID:sessionId ` , uuid , req . sessionID ) ;
2020-05-16 22:17:20 -04:00
}
2017-11-30 14:24:13 -05:00
2019-09-11 02:02:07 -04:00
// Force session check for all connected socket.io clients with the same session id
2021-02-03 23:59:08 -07:00
sockets . in ( ` sess_ ${ req . sessionID } ` ) . emit ( 'checkSession' , uid ) ;
2017-08-23 12:13:52 -04:00
2020-11-20 16:06:26 -05:00
plugins . hooks . fire ( 'action:user.loggedIn' , { uid : uid , req : req } ) ;
2019-09-11 02:02:07 -04:00
} catch ( err ) {
req . session . destroy ( ) ;
throw err ;
}
2016-03-08 12:17:12 +02:00
} ;
2016-02-26 16:45:44 +02:00
2019-09-11 02:02:07 -04:00
authenticationController . localLogin = async function ( req , username , password , next ) {
2016-01-10 10:26:47 +02:00
if ( ! username ) {
return next ( new Error ( '[[error:invalid-username]]' ) ) ;
2015-06-17 14:13:29 -04:00
}
2017-04-22 14:38:43 -04:00
if ( ! password || ! utils . isPasswordValid ( password ) ) {
return next ( new Error ( '[[error:invalid-password]]' ) ) ;
}
2020-11-06 08:40:00 -05:00
if ( password . length > 512 ) {
2017-04-22 14:38:43 -04:00
return next ( new Error ( '[[error:password-too-long]]' ) ) ;
}
2020-10-11 21:49:37 -04:00
const userslug = slugify ( username ) ;
2019-09-11 02:02:07 -04:00
const uid = await user . getUidByUserslug ( userslug ) ;
try {
2020-12-14 09:20:41 +03:00
const [ userData , isAdminOrGlobalMod , canLoginIfBanned ] = await Promise . all ( [
2019-10-16 13:51:37 -04:00
user . getUserFields ( uid , [ 'uid' , 'passwordExpiry' ] ) ,
2019-09-11 02:02:07 -04:00
user . isAdminOrGlobalMod ( uid ) ,
2020-12-14 09:20:41 +03:00
user . bans . canLoginIfBanned ( uid ) ,
2019-09-11 02:02:07 -04:00
] ) ;
2015-06-17 14:13:29 -04:00
2019-09-11 02:02:07 -04:00
userData . isAdminOrGlobalMod = isAdminOrGlobalMod ;
2017-05-05 19:50:50 -04:00
2020-12-14 09:20:41 +03:00
if ( ! canLoginIfBanned ) {
2021-09-21 17:04:17 -04:00
return next ( await getBanError ( uid ) ) ;
2019-09-11 02:02:07 -04:00
}
2018-07-27 11:54:23 -04:00
2020-12-14 09:20:41 +03:00
// Doing this after the ban check, because user's privileges might change after a ban expires
const hasLoginPrivilege = await privileges . global . can ( 'local:login' , uid ) ;
if ( parseInt ( uid , 10 ) && ! hasLoginPrivilege ) {
return next ( new Error ( '[[error:local-login-disabled]]' ) ) ;
}
2019-09-11 02:02:07 -04:00
const passwordMatch = await user . isPasswordCorrect ( uid , password , req . ip ) ;
if ( ! passwordMatch ) {
return next ( new Error ( '[[error:invalid-login-credentials]]' ) ) ;
}
next ( null , userData , '[[success:authentication-successful]]' ) ;
} catch ( err ) {
next ( err ) ;
}
2015-06-17 14:13:29 -04:00
} ;
2019-10-22 13:38:36 -04:00
const destroyAsync = util . promisify ( ( req , callback ) => req . session . destroy ( callback ) ) ;
2019-09-11 02:02:07 -04:00
authenticationController . logout = async function ( req , res , next ) {
2018-01-31 15:20:17 -05:00
if ( ! req . loggedIn || ! req . sessionID ) {
2020-05-27 12:15:02 -04:00
res . clearCookie ( nconf . get ( 'sessionKey' ) , meta . configs . cookie . get ( ) ) ;
2017-05-23 22:09:25 -04:00
return res . status ( 200 ) . send ( 'not-logged-in' ) ;
}
2021-02-06 14:10:15 -07:00
const { uid } = req ;
const { sessionID } = req ;
2018-11-07 12:34:12 -05:00
2019-09-11 02:02:07 -04:00
try {
await user . auth . revokeSession ( sessionID , uid ) ;
req . logout ( ) ;
2019-10-22 13:38:36 -04:00
await destroyAsync ( req ) ;
2020-04-13 13:26:17 -04:00
res . clearCookie ( nconf . get ( 'sessionKey' ) , meta . configs . cookie . get ( ) ) ;
2019-09-11 02:02:07 -04:00
req . uid = 0 ;
req . headers [ 'x-csrf-token' ] = req . csrfToken ( ) ;
await user . setUserField ( uid , 'lastonline' , Date . now ( ) - ( meta . config . onlineCutoff * 60000 ) ) ;
await db . sortedSetAdd ( 'users:online' , Date . now ( ) - ( meta . config . onlineCutoff * 60000 ) , uid ) ;
2020-11-20 16:06:26 -05:00
await plugins . hooks . fire ( 'static:user.loggedOut' , { req : req , res : res , uid : uid , sessionID : sessionID } ) ;
2019-09-11 02:02:07 -04:00
// Force session check for all connected socket.io clients with the same session id
2021-02-03 23:59:08 -07:00
sockets . in ( ` sess_ ${ sessionID } ` ) . emit ( 'checkSession' , 0 ) ;
2019-09-11 02:02:07 -04:00
const payload = {
2021-02-03 23:59:08 -07:00
next : ` ${ nconf . get ( 'relative_path' ) } / ` ,
2019-09-11 02:02:07 -04:00
} ;
2020-11-20 16:06:26 -05:00
plugins . hooks . fire ( 'filter:user.logout' , payload ) ;
2020-05-27 12:15:02 -04:00
if ( req . body . noscript === 'true' ) {
return res . redirect ( payload . next ) ;
}
2019-09-11 02:02:07 -04:00
res . status ( 200 ) . send ( payload ) ;
} catch ( err ) {
next ( err ) ;
}
2015-06-17 14:13:29 -04:00
} ;
2021-09-21 17:04:17 -04:00
async function getBanError ( uid ) {
2019-09-11 02:02:07 -04:00
try {
const banInfo = await user . getLatestBanInfo ( uid ) ;
2017-05-05 19:50:50 -04:00
2019-09-11 02:02:07 -04:00
if ( ! banInfo . reason ) {
2021-09-21 17:04:17 -04:00
banInfo . reason = '[[user:info.banned-no-reason]]' ;
2017-05-05 19:50:50 -04:00
}
2021-09-21 17:04:17 -04:00
const err = new Error ( banInfo . reason ) ;
err . data = banInfo ;
return err ;
2019-09-11 02:02:07 -04:00
} catch ( err ) {
if ( err . message === 'no-ban-info' ) {
2021-09-21 17:04:17 -04:00
return new Error ( '[[error:user-banned]]' ) ;
2019-09-11 02:02:07 -04:00
}
throw err ;
}
2017-05-05 19:50:50 -04:00
}
2019-09-11 02:02:07 -04:00
require ( '../promisify' ) ( authenticationController , [ 'register' , 'registerComplete' , 'registerAbort' , 'login' , 'localLogin' , 'logout' ] ) ;