@ -18,12 +18,17 @@ class UserController extends SiteController {
async start ( ) {
const { dtp } = this ;
const {
csrfToken : csrfTokenService ,
limiter : limiterService ,
otpAuth : otpAuthService ,
session : sessionService ,
} = dtp . services ;
const upload = this . createMulter ( ) ;
const upload = this . createMulter ( 'user' , {
limits : {
fileSize : 1024 * 1000 * 5 , // 5MB
} ,
} ) ;
const router = express . Router ( ) ;
dtp . app . use ( '/user' , router ) ;
@ -60,8 +65,10 @@ class UserController extends SiteController {
return next ( ) ;
}
router . param ( 'username' , this . populateUsername . bind ( this ) ) ;
router . param ( 'userId' , this . populateUserId . bind ( this ) ) ;
router . param ( 'localUsername' , this . populateLocalUsername . bind ( this ) ) ;
router . param ( 'coreUsername' , this . populateCoreUsername . bind ( this ) ) ;
router . param ( 'localUserId' , this . populateLocalUserId . bind ( this ) ) ;
router . param ( 'coreUserId' , this . populateCoreUserId . bind ( this ) ) ;
router . post (
@ -73,7 +80,7 @@ class UserController extends SiteController {
) ;
router . post (
'/: u serId/profile-photo',
'/: localU serId/profile-photo',
limiterService . createMiddleware ( limiterService . config . user . postProfilePhoto ) ,
checkProfileOwner ,
upload . single ( 'imageFile' ) ,
@ -81,7 +88,7 @@ class UserController extends SiteController {
) ;
router . post (
'/: u serId/settings',
'/: localU serId/settings',
limiterService . createMiddleware ( limiterService . config . user . postUpdateSettings ) ,
checkProfileOwner ,
upload . none ( ) ,
@ -91,6 +98,7 @@ class UserController extends SiteController {
router . post (
'/' ,
limiterService . createMiddleware ( limiterService . config . user . postCreate ) ,
csrfTokenService . middleware ( { name : 'user-create' } ) ,
this . postCreateUser . bind ( this ) ,
) ;
@ -124,7 +132,7 @@ class UserController extends SiteController {
) ;
router . get (
'/: userId /settings',
'/: localUsername /settings',
limiterService . createMiddleware ( limiterService . config . user . getSettings ) ,
authRequired ,
otpMiddleware ,
@ -132,7 +140,7 @@ class UserController extends SiteController {
this . getUserSettingsView . bind ( this ) ,
) ;
router . get (
'/: u sername',
'/: localU sername',
limiterService . createMiddleware ( limiterService . config . user . getUserProfile ) ,
authRequired ,
otpMiddleware ,
@ -148,54 +156,127 @@ class UserController extends SiteController {
) ;
}
async populateUsername ( req , res , next , username ) {
const { user : userService } = this . dtp . services ;
async populateCoreUsername ( req , res , next , coreUsername ) {
const {
logan : loganService ,
user : userService ,
} = this . dtp . services ;
try {
res . locals . userProfile = await userService . getPublicProfile ( 'User' , username ) ;
if ( ! res . locals . userProfile ) {
throw new SiteError ( 404 , 'Member not found' ) ;
res . locals . username = userService . filterUsername ( coreUsername ) ;
res . locals . userProfileId = await userService . getCoreUserId ( res . locals . username ) ;
if ( ! res . locals . userProfileId ) {
throw new SiteError ( 404 , 'Core member not found' ) ;
}
return next ( ) ;
// manually chain over to the ID parameter resolver
return this . populateCoreUserId ( req , res , next , res . locals . userProfileId ) ;
} catch ( error ) {
this . log . error ( 'failed to populate username with public profile' , { username , error } ) ;
this . log . error ( 'failed to populate core username' , { coreUsername , error } ) ;
loganService . sendRequestEvent ( module . exports , req , {
level : 'error' ,
event : 'populateCoreUsername' ,
message : ` failed to populate Core user: ${ error . message } ` ,
data : { coreUsername , error } ,
} ) ;
return next ( error ) ;
}
}
async populateUserId ( req , res , next , userId ) {
const { user : userService } = this . dtp . services ;
try {
userId = mongoose . Types . ObjectId ( userId ) ;
} catch ( error ) {
return next ( new SiteError ( 406 , 'Invalid User' ) ) ;
}
async populateCoreUserId ( req , res , next , coreUserId ) {
const {
logan : loganService ,
user : userService ,
} = this . dtp . services ;
try {
res . locals . userProfile = await userService . getUserAccount ( userId ) ;
res . locals . userProfileId = mongoose . Types . ObjectId ( coreUserId ) ;
if ( req . user && ( req . user . type === 'CoreUser' ) && req . user . _id . equals ( res . locals . userProfileId ) ) {
res . locals . userProfile = await userService . getCoreUserAccount ( res . locals . userProfileId ) ;
} else {
res . locals . userProfile = await userService . getCoreUserProfile ( res . locals . userProfileId ) ;
}
if ( ! res . locals . userProfile ) {
throw new SiteError ( 404 , 'Core member not found' ) ;
}
return next ( ) ;
} catch ( error ) {
this . log . error ( 'failed to populate userId' , { userId , error } ) ;
loganService . sendRequestEvent ( module . exports , req , {
level : 'error' ,
event : 'populateCoreUserId' ,
message : ` failed to populate core user: ${ error . message } ` ,
data : { coreUserId , error } ,
} ) ;
return next ( error ) ;
}
}
async populateCoreUserId ( req , res , next , coreUserId ) {
const { coreNode : coreNodeService } = this . dtp . services ;
async populateLocalUsername ( req , res , next , username ) {
const {
logan : loganService ,
user : userService ,
} = this . dtp . services ;
try {
coreUserId = mongoose . Types . ObjectId ( coreUserId ) ;
res . locals . username = userService . filterUsername ( username ) ;
res . locals . userProfileId = await userService . getLocalUserId ( res . locals . username ) ;
if ( ! res . locals . userProfileId ) {
throw new SiteError ( 404 , 'Local member not found' ) ;
}
if ( req . user && ( req . user . type === 'User' ) && req . user . _id . equals ( res . locals . userProfileId ) ) {
res . locals . userProfile = await userService . getLocalUserAccount ( res . locals . userProfileId ) ;
} else {
res . locals . userProfile = await userService . getLocalUserProfile ( res . locals . userProfileId ) ;
}
return next ( ) ;
} catch ( error ) {
return next ( new SiteError ( 406 , 'Invalid User' ) ) ;
this . log . error ( 'failed to populate local username' , { username , error } ) ;
loganService . sendRequestEvent ( module . exports , req , {
level : 'error' ,
event : 'populateLocalUsername' ,
message : ` failed to populate local user: ${ error . message } ` ,
data : { username , error } ,
} ) ;
return next ( error ) ;
}
}
async populateLocalUserId ( req , res , next , userId ) {
const {
logan : loganService ,
user : userService ,
} = this . dtp . services ;
try {
res . locals . userProfile = await coreNodeService . getUserByLocalId ( coreUserId ) ;
res . locals . userProfileId = mongoose . Types . ObjectId ( userId ) ;
if ( req . user && ( req . user . type === 'User' ) && req . user . _id . equals ( res . locals . userProfileId ) ) {
res . locals . userProfile = await userService . getLocalUserAccount ( res . locals . userProfileId ) ;
} else {
res . locals . userProfile = await userService . getLocalUserProfile ( res . locals . userProfileId ) ;
}
if ( ! res . locals . userProfile ) {
throw new SiteError ( 404 , 'Local member not found' ) ;
}
return next ( ) ;
} catch ( error ) {
this . log . error ( 'failed to populate coreUserId' , { coreUserId , error } ) ;
loganService . sendRequestEvent ( module . exports , req , {
level : 'error' ,
event : 'populateLocalUserId' ,
message : ` failed to populate local user: ${ error . message } ` ,
data : { userId , error } ,
} ) ;
return next ( error ) ;
}
}
async postCreateUser ( req , res , next ) {
const { user : userService } = this . dtp . services ;
const {
logan : loganService ,
user : userService ,
} = this . dtp . services ;
try {
// verify that the request has submitted a captcha
if ( ( typeof req . body . captcha !== 'string' ) || req . body . captcha . length === 0 ) {
@ -213,11 +294,42 @@ class UserController extends SiteController {
// create the user account
res . locals . user = await userService . create ( req . body ) ;
const form = Object . assign ( req . body ) ;
if ( form . password ) {
delete form . password ;
}
if ( form . passwordv ) {
delete form . passwordv ;
}
loganService . sendRequestEvent ( module . exports , req , {
level : 'info' ,
event : 'postCreateUser' ,
data : {
form ,
user : {
_id : res . locals . user . _id ,
username : res . locals . user . username ,
} ,
} ,
} ) ;
// log the user in
req . login ( res . locals . user , ( error ) => {
if ( error ) {
loganService . sendRequestEvent ( module . exports , req , {
level : 'error' ,
event : 'postCreateUser' ,
message : ` failed to start user session: ${ error . message } ` ,
data : { error } ,
} ) ;
return next ( error ) ;
}
loganService . sendRequestEvent ( module . exports , req , {
level : 'info' ,
event : 'postCreateUser' ,
message : 'user session started' ,
} ) ;
res . redirect ( ` /user/ ${ res . locals . user . username } ` ) ;
} ) ;
} catch ( error ) {
@ -227,19 +339,35 @@ class UserController extends SiteController {
}
async postProfilePhoto ( req , res ) {
const { user : userService } = this . dtp . services ;
const {
logan : loganService ,
user : userService ,
} = this . dtp . services ;
try {
const displayList = this . createDisplayList ( 'profile-photo' ) ;
await userService . updatePhoto ( req . user , req . file ) ;
const displayList = this . createDisplayList ( 'profile-photo' ) ;
displayList . showNotification (
'Profile photo updated successfully.' ,
'success' ,
'bottom-center' ,
2000 ,
) ;
loganService . sendRequestEvent ( module . exports , req , {
level : 'info' ,
event : 'postProfilePhoto' ,
message : 'profile photo updated' ,
} ) ;
res . status ( 200 ) . json ( { success : true , displayList } ) ;
} catch ( error ) {
this . log . error ( 'failed to update profile photo' , { error } ) ;
loganService . sendRequestEvent ( module . exports , req , {
level : 'error' ,
event : 'postProfilePhoto' ,
message : ` failed to update profile photo: ${ error . message } ` ,
data : { error } ,
} ) ;
return res . status ( error . statusCode || 500 ) . json ( {
success : false ,
message : error . message ,
@ -248,19 +376,35 @@ class UserController extends SiteController {
}
async postHeaderImage ( req , res ) {
const { user : userService } = this . dtp . services ;
const {
logan : loganService ,
user : userService ,
} = this . dtp . services ;
try {
const displayList = this . createDisplayList ( 'header-image' ) ;
await userService . updateHeaderImage ( req . user , req . file ) ;
const displayList = this . createDisplayList ( 'header-image' ) ;
displayList . showNotification (
'Header image updated successfully.' ,
'success' ,
'bottom-center' ,
2000 ,
) ;
loganService . sendRequestEvent ( module . exports , req , {
level : 'info' ,
event : 'postHeaderImage' ,
message : 'header image updated' ,
} ) ;
res . status ( 200 ) . json ( { success : true , displayList } ) ;
} catch ( error ) {
this . log . error ( 'failed to update header image' , { error } ) ;
loganService . sendRequestEvent ( module . exports , req , {
level : 'error' ,
event : 'postHeaderImage' ,
message : ` failed to update header image: ${ error . message } ` ,
data : { error } ,
} ) ;
return res . status ( error . statusCode || 500 ) . json ( {
success : false ,
message : error . message ,
@ -269,16 +413,30 @@ class UserController extends SiteController {
}
async postUpdateCoreSettings ( req , res ) {
const { coreNode : coreNodeService } = this . dtp . services ;
const {
coreNode : coreNodeService ,
logan : loganService ,
} = this . dtp . services ;
try {
const displayList = this . createDisplayList ( 'app-settings' ) ;
await coreNodeService . updateUserSettings ( req . user , req . body ) ;
const displayList = this . createDisplayList ( 'app-settings' ) ;
displayList . reload ( ) ;
loganService . sendRequestEvent ( module . exports , req , {
level : 'info' ,
event : 'postUpdateCoreSettings' ,
message : 'CoreUser settings updated' ,
} ) ;
res . status ( 200 ) . json ( { success : true , displayList } ) ;
} catch ( error ) {
this . log . error ( 'failed to update CoreUser settings' , { error } ) ;
loganService . sendRequestEvent ( module . exports , req , {
level : 'error' ,
event : 'postUpdateCoreSettings' ,
message : ` failed to update CoreUser settings: ${ error . message } ` ,
data : { error } ,
} ) ;
return res . status ( error . statusCode || 500 ) . json ( {
success : false ,
message : error . message ,
@ -287,16 +445,30 @@ class UserController extends SiteController {
}
async postUpdateSettings ( req , res ) {
const { user : userService } = this . dtp . services ;
const {
logan : loganService ,
user : userService ,
} = this . dtp . services ;
try {
await userService . updateSettings ( req . user , req . body ) ;
const displayList = this . createDisplayList ( 'app-settings' ) ;
displayList . reload ( ) ;
loganService . sendRequestEvent ( module . exports , req , {
level : 'info' ,
event : 'postUpdateSettings' ,
message : 'account settings updates' ,
} ) ;
res . status ( 200 ) . json ( { success : true , displayList } ) ;
} catch ( error ) {
this . log . error ( 'failed to update account settings' , { error } ) ;
loganService . sendRequestEvent ( module . exports , req , {
level : 'error' ,
event : 'postUpdateSettings' ,
message : ` failed to update account settings: ${ error . message } ` ,
data : { error } ,
} ) ;
return res . status ( error . statusCode || 500 ) . json ( {
success : false ,
message : error . message ,
@ -309,13 +481,26 @@ class UserController extends SiteController {
}
async getOtpDisable ( req , res ) {
const { otpAuth : otpAuthService } = this . dtp . services ;
const {
logan : loganService ,
otpAuth : otpAuthService ,
} = this . dtp . services ;
try {
await otpAuthService . destroyOtpSession ( req , 'Account' ) ;
await otpAuthService . removeForUser ( req . user , 'Account' ) ;
loganService . sendRequestEvent ( module . exports , req , {
level : 'info' ,
event : 'getOtpDisable' ,
message : 'one-time passwords disabled' ,
} ) ;
res . render ( 'user/otp-disabled' ) ;
} catch ( error ) {
this . log . error ( 'failed to disable OTP service for Account' , { error } ) ;
loganService . sendRequestEvent ( module . exports , req , {
level : 'error' ,
event : 'getOtpDisable' ,
message : ` failed to disable OTP: ${ error . message } ` ,
data : { error } ,
} ) ;
res . status ( error . statusCode || 500 ) . json ( {
success : false ,
message : error . message ,
@ -324,44 +509,71 @@ class UserController extends SiteController {
}
async getCoreUserSettingsView ( req , res , next ) {
const { otpAuth : otpAuthService } = this . dtp . services ;
const {
logan : loganService ,
otpAuth : otpAuthService ,
} = this . dtp . services ;
try {
res . locals . hasOtpAccount = await otpAuthService . isUserProtected ( req . user , 'Account' ) ;
res . locals . startTab = req . query . st || 'watch' ;
res . render ( 'user/settings-core' ) ;
} catch ( error ) {
this . log . error ( 'failed to render CoreUser settings view' , { error } ) ;
loganService . sendRequestEvent ( module . exports , req , {
level : 'error' ,
event : 'getCoreUserSettingsView' ,
message : ` failed to render the view: ${ error . message } ` ,
data : { error } ,
} ) ;
return next ( error ) ;
}
}
async getUserSettingsView ( req , res , next ) {
const { otpAuth : otpAuthService } = this . dtp . services ;
const {
logan : loganService ,
otpAuth : otpAuthService ,
} = this . dtp . services ;
try {
res . locals . hasOtpAccount = await otpAuthService . isUserProtected ( req . user , 'Account' ) ;
res . locals . startTab = req . query . st || 'watch' ;
res . render ( 'user/settings' ) ;
} catch ( error ) {
this . log . error ( 'failed to render user settings view' , { error } ) ;
loganService . sendRequestEvent ( module . exports , req , {
level : 'error' ,
event : 'getUserSettingsView' ,
message : ` failed to render the view: ${ error . message } ` ,
data : { error } ,
} ) ;
return next ( error ) ;
}
}
async getUserView ( req , res , next ) {
const { comment : commentService } = this . dtp . services ;
const {
comment : commentService ,
logan : loganService ,
} = this . dtp . services ;
try {
res . locals . pageTitle = ` ${ res . locals . userProfile . username } 's Profile ` ;
res . locals . pagination = this . getPaginationParameters ( req , 20 ) ;
res . locals . commentHistory = await commentService . getForAuthor ( req . user , res . locals . pagination ) ;
res . render ( 'user/profile' ) ;
} catch ( error ) {
this . log . error ( 'failed to produce user profile view' , { error } ) ;
loganService . sendRequestEvent ( module . exports , req , {
level : 'error' ,
event : 'getUserView' ,
message : ` failed to render the view: ${ error . message } ` ,
data : { error } ,
} ) ;
return next ( error ) ;
}
}
async deleteProfilePhoto ( req , res ) {
const { user : userService } = this . dtp . services ;
const {
logan : loganService ,
user : userService ,
} = this . dtp . services ;
try {
const displayList = this . createDisplayList ( 'app-settings' ) ;
await userService . removePhoto ( req . user ) ;
@ -371,9 +583,19 @@ class UserController extends SiteController {
'bottom-center' ,
2000 ,
) ;
loganService . sendRequestEvent ( module . exports , req , {
level : 'info' ,
event : 'deleteProfilePhoto' ,
message : 'profile photo removed' ,
} ) ;
res . status ( 200 ) . json ( { success : true , displayList } ) ;
} catch ( error ) {
this . log . error ( 'failed to remove profile photo' , { error } ) ;
loganService . sendRequestEvent ( module . exports , req , {
level : 'error' ,
event : 'deleteProfilePhoto' ,
message : ` failed to remove profile photo: ${ error . message } ` ,
data : { error } ,
} ) ;
return res . status ( error . statusCode || 500 ) . json ( {
success : false ,
message : error . message ,
@ -382,7 +604,10 @@ class UserController extends SiteController {
}
async deleteHeaderImage ( req , res ) {
const { user : userService } = this . dtp . services ;
const {
logan : loganService ,
user : userService ,
} = this . dtp . services ;
try {
const displayList = this . createDisplayList ( 'remove-header-image' ) ;
await userService . removeHeaderImage ( req . user ) ;
@ -392,9 +617,19 @@ class UserController extends SiteController {
'bottom-center' ,
2000 ,
) ;
loganService . sendRequestEvent ( module . exports , req , {
level : 'info' ,
event : 'deleteHeaderImage' ,
message : 'profile header image removed' ,
} ) ;
res . status ( 200 ) . json ( { success : true , displayList } ) ;
} catch ( error ) {
this . log . error ( 'failed to remove header image' , { error } ) ;
loganService . sendRequestEvent ( module . exports , req , {
level : 'error' ,
event : 'deleteHeaderImage' ,
message : ` failed to remove header image: ${ error . message } ` ,
data : { error } ,
} ) ;
return res . status ( error . statusCode || 500 ) . json ( {
success : false ,
message : error . message ,
@ -406,5 +641,6 @@ class UserController extends SiteController {
module . exports = {
slug : 'user' ,
name : 'user' ,
className : 'UserController' ,
create : async ( dtp ) => { return new UserController ( dtp ) ; } ,
} ;