Browse Source

User refactor start

pull/1/head
Rob Colbert 3 years ago
parent
commit
7bf1acbc8b
  1. 45
      app/models/core-user.js
  2. 27
      app/models/lib/user-types.js
  3. 20
      app/models/user.js
  4. 92
      app/services/core-node.js
  5. 2
      app/services/oauth2.js
  6. 21
      app/services/session.js

45
app/models/core-user.js

@ -0,0 +1,45 @@
// core-user.js
// Copyright (C) 2022 DTP Technologies, LLC
// License: Apache-2.0
'use strict';
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const { ResourceStats, ResourceStatsDefaults } = require('./lib/resource-stats');
const { DTP_THEME_LIST, UserFlagsSchema, UserPermissionsSchema, UserOptInSchema } = require('./lib/user-types');
const CoreUserSchema = new Schema({
created: { type: Date, default: Date.now, required: true, index: -1 },
updated: { type: Date, default: Date.now, required: true, index: -1 },
core: { type: Schema.ObjectId, required: true, ref: 'CoreNode' },
coreUserId: { type: Schema.ObjectId, required: true },
username: { type: String, required: true },
username_lc: { type: String, required: true, lowercase: true },
displayName: { type: String },
bio: { type: String, maxlength: 300 },
flags: { type: UserFlagsSchema, select: false },
permissions: { type: UserPermissionsSchema, select: false },
optIn: { type: UserOptInSchema, required: true, select: false },
theme: { type: String, enum: DTP_THEME_LIST, default: 'dtp-light', required: true },
stats: { type: ResourceStats, default: ResourceStatsDefaults, required: true },
});
CoreUserSchema.index({
core: 1,
coreUserId: 1,
}, {
unique: true,
name: 'core_userId_unique',
});
CoreUserSchema.index({
core: 1,
username_lc: 1,
}, {
unique: true,
name: 'core_username_lc_unique',
});
module.exports = mongoose.model('CoreUser', CoreUserSchema);

27
app/models/lib/user-types.js

@ -0,0 +1,27 @@
// user-types.js
// Copyright (C) 2022 DTP Technologies, LLC
// License: Apache-2.0
'use strict';
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
module.exports.DTP_THEME_LIST = ['dtp-light', 'dtp-dark'];
module.exports.UserFlagsSchema = new Schema({
isAdmin: { type: Boolean, default: false, required: true },
isModerator: { type: Boolean, default: false, required: true },
});
module.exports.UserPermissionsSchema = new Schema({
canLogin: { type: Boolean, default: true, required: true },
canChat: { type: Boolean, default: true, required: true },
canComment: { type: Boolean, default: true, required: true },
canReport: { type: Boolean, default: true, required: true },
});
module.exports.UserOptInSchema = new Schema({
system: { type: Boolean, default: true, required: true },
marketing: { type: Boolean, default: true, required: true },
});

20
app/models/user.js

@ -9,25 +9,7 @@ const mongoose = require('mongoose');
const Schema = mongoose.Schema; const Schema = mongoose.Schema;
const { ResourceStats, ResourceStatsDefaults } = require('./lib/resource-stats'); const { ResourceStats, ResourceStatsDefaults } = require('./lib/resource-stats');
const { DTP_THEME_LIST, UserFlagsSchema, UserPermissionsSchema, UserOptInSchema } = require('./lib/user-types');
const DTP_THEME_LIST = ['dtp-light', 'dtp-dark'];
const UserFlagsSchema = new Schema({
isAdmin: { type: Boolean, default: false, required: true },
isModerator: { type: Boolean, default: false, required: true },
});
const UserPermissionsSchema = new Schema({
canLogin: { type: Boolean, default: true, required: true },
canChat: { type: Boolean, default: true, required: true },
canComment: { type: Boolean, default: true, required: true },
canReport: { type: Boolean, default: true, required: true },
});
const UserOptInSchema = new Schema({
system: { type: Boolean, default: true, required: true },
marketing: { type: Boolean, default: true, required: true },
});
const UserSchema = new Schema({ const UserSchema = new Schema({
created: { type: Date, default: Date.now, required: true, index: -1 }, created: { type: Date, default: Date.now, required: true, index: -1 },

92
app/services/core-node.js

@ -10,8 +10,8 @@ const fetch = require('node-fetch'); // jshint ignore:line
const mongoose = require('mongoose'); const mongoose = require('mongoose');
const CoreNode = mongoose.model('CoreNode'); const CoreNode = mongoose.model('CoreNode');
const CoreUser = mongoose.model('CoreUser');
const CoreNodeRequest = mongoose.model('CoreNodeRequest'); const CoreNodeRequest = mongoose.model('CoreNodeRequest');
const User = mongoose.model('User');
const passport = require('passport'); const passport = require('passport');
const OAuth2Strategy = require('passport-oauth2'); const OAuth2Strategy = require('passport-oauth2');
@ -41,6 +41,11 @@ class CoreNodeService extends SiteService {
constructor (dtp) { constructor (dtp) {
super(dtp, module.exports); super(dtp, module.exports);
this.populateCoreUser = [
{
path: 'core'
},
];
} }
async start ( ) { async start ( ) {
@ -74,11 +79,20 @@ class CoreNodeService extends SiteService {
router.get( router.get(
coreAuthCallbackUri, coreAuthCallbackUri,
(req, res, next) => { (req, res, next) => {
this.log.debug('Core auth callback'); this.log.debug('Core auth callback', {
strategy: coreAuthStrategyName,
body: req.body,
params: req.params,
query: req.query,
});
return next(); return next();
}, },
passport.authenticate(coreAuthStrategyName, { failureRedirect: '/' }), passport.authenticate(coreAuthStrategyName, { failureRedirect: '/' }),
async (req, res) => { async (req, res) => {
this.log.info('tagging session as Core', { coreId: core._id });
req.session.userType = 'Core';
req.session.coreId = core._id;
// req.login(user, (error) => { // req.login(user, (error) => {
// if (error) { // if (error) {
// return next(error); // return next(error);
@ -109,19 +123,72 @@ class CoreNodeService extends SiteService {
clientSecret: core.oauth.clientSecret, clientSecret: core.oauth.clientSecret,
callbackURL, callbackURL,
}, },
async (accessToken, refreshToken, profile, cb) => { async (accessToken, refreshToken, params, profile, cb) => {
const NOW = new Date();
this.log.info('Core login', { this.log.info('Core login', {
name: core.meta.name,
profile,
accessToken, accessToken,
refreshToken, refreshToken,
params,
profile,
}); });
User.findOrCreate({ exampleId: profile.id }, function (err, user) { try {
return cb(err, user); const coreUserId = mongoose.Types.ObjectId(params.coreUserId);
}); const user = await CoreUser.findOneAndUpdate(
{
core: core._id,
coreUserId,
},
{
$setOnInsert: {
created: NOW,
core: core._id,
coreUserId,
flags: {
isAdmin: false,
isModerator: false,
},
permissions: {
canLogin: true,
canChat: true,
canComment: true,
canReport: true,
},
optIn: {
system: true,
marketing: false,
},
theme: 'dtp-light',
stats: {
uniqueVisitCount: 0,
totalVisitCount: 0,
},
},
$set: {
updated: NOW,
username: params.username,
username_lc: params.username_lc,
displayName: params.displayName,
bio: params.bio,
},
},
{
upsert: true,
new: true,
},
);
return cb(null, user.toObject());
} catch (error) {
return cb(error);
}
}, },
); );
// const old = coreAuthStrategy._loadUserProfile;
coreAuthStrategy._loadUserProfile = async (accessToken, done) => {
this.log.info('loadUserProfile', { accessToken });
return done(null);
};
this.log.info('registering Core auth strategy', { this.log.info('registering Core auth strategy', {
name: coreAuthStrategyName, name: coreAuthStrategyName,
host: core.address.host, host: core.address.host,
@ -330,6 +397,15 @@ class CoreNodeService extends SiteService {
const cores = await q.lean(); const cores = await q.lean();
return cores; return cores;
} }
async getUserByLocalId (userId) {
const user = await CoreUser
.findOne({ _id: userId })
.select('+flags +permissions +optIn')
.populate(this.populateCoreUser)
.lean();
return user;
}
} }
module.exports = { module.exports = {

2
app/services/oauth2.js

@ -85,7 +85,7 @@ class OAuth2Service extends SiteService {
this.log.debug('OAuth2 client loaded', { clientID }); this.log.debug('OAuth2 client loaded', { clientID });
return done(null, client); return done(null, client);
} catch (error) { } catch (error) {
this.log.error('failed to deserialize OAuth2 client', { clientId, error }); this.log.error('failed to deserialize OAuth2 client', { clientID, error });
return done(error); return done(error);
} }
} }

21
app/services/session.js

@ -64,13 +64,26 @@ class SessionService extends SiteService {
} }
async serializeUser (user, done) { async serializeUser (user, done) {
return done(null, user._id); if (user.coreUserId) {
return done(null, `core:${user._id}`);
}
return done(null, `local:${user._id}`);
} }
async deserializeUser (userId, done) { async deserializeUser (userTag, done) {
const { user: userService } = this.dtp.services; const { coreNode: coreNodeService, user: userService } = this.dtp.services;
const [serviceName, userId] = userTag.split(':');
this.log.debug('load user session', { userTag, serviceName, userId });
try { try {
const user = await userService.getUserAccount(userId); let user;
switch (serviceName) {
case 'core':
user = await coreNodeService.getUserByLocalId(userId);
break;
case 'local':
user = await userService.getUserAccount(userId);
break;
}
return done(null, user); return done(null, user);
} catch (error) { } catch (error) {
this.log.error('failed to deserialize user from session', { error }); this.log.error('failed to deserialize user from session', { error });

Loading…
Cancel
Save