feat(admin): added env var setting so only admin can create new users

This commit is contained in:
Daniel Gonzalez
2025-05-19 12:17:09 -04:00
parent af83c06b43
commit 05a0620663
6 changed files with 144 additions and 92 deletions
+7 -1
View File
@@ -150,7 +150,13 @@ export default {
// v2 Circuit Relay server (FullStack.cash) // v2 Circuit Relay server (FullStack.cash)
// '/ip4/78.46.129.7/tcp/4001/p2p/12D3KooWFQ11GQ5NubsJGhYZ4X3wrAGimLevxfm6HPExCrMYhpSL' // '/ip4/78.46.129.7/tcp/4001/p2p/12D3KooWFQ11GQ5NubsJGhYZ4X3wrAGimLevxfm6HPExCrMYhpSL'
] ],
// END IPFS CONFIGURATION // END IPFS CONFIGURATION
// Account Configuration
disableNewAccounts: process.env.DISABLE_NEW_ACCOUNTS ? true : false,
// Admin password
adminPassword: process.env.ADMIN_PASSWORD
} }
+26 -58
View File
@@ -51,70 +51,37 @@ class Admin {
// used by the Listing Manager and test scripts, in order access private API // used by the Listing Manager and test scripts, in order access private API
// functions. // functions.
async createSystemUser () { async createSystemUser () {
// Create the system user.
try { try {
context.password = _this._randomString(20) const context = {
email: 'system@system.com',
const options = { name: 'admin',
method: 'POST', password: _this.config.adminPassword || _this._randomString(20),
url: `${LOCALHOST}/users`, type: 'admin'
data: {
user: {
email: 'system@system.com',
password: context.password,
name: 'admin'
}
}
} }
const result = await _this.axios.request(options) // Check if the user already exists
// console.log('admin.data: ', result.data) let adminUser = await _this.User.findOne({ email: context.email })
context.email = result.data.user.email if (adminUser) {
context.id = result.data.user._id // Update the password
context.token = result.data.token adminUser.password = context.password
} else {
// Create a new admin user
adminUser = new _this.User(context)
}
// Update context with the new user id and token
context.id = adminUser._id
context.token = await adminUser.generateToken()
// Get the mongoDB entry // Save the user
const user = await _this.User.findById(context.id) await adminUser.save()
// Change the user type to admin // Write the user data to the JSON file
user.type = 'admin' await _this.jsonFiles.writeJSON(context, JSON_PATH)
// console.log(`user created: ${JSON.stringify(user, null, 2)}`)
// Save the user model.
await user.save()
// console.log(`admin user created: ${JSON.stringify(result.body, null, 2)}`)
// console.log(`with password: ${context.password}`)
// Write out the system user information to a JSON file that external
// applications like the Task Manager and the test scripts can access.
await jsonFiles.writeJSON(context, JSON_PATH)
// console.log('context: ', context)
// console.log('JSON_PATH: ', JSON_PATH)
return context return context
} catch (err) { } catch (error) {
// Handle existing system user. console.log('Error in admin.js/createSystemUser()')
if (err.response.status === 422) { throw error
try {
// Delete the existing user
await _this.deleteExistingSystemUser()
// Call this function again.
return _this.createSystemUser()
} catch (err2) {
console.error(
'Error in admin.js/createSystemUser() while trying generate new system user.'
)
// process.end(1)
throw err2
}
} else {
console.log('Error in admin.js/createSystemUser: ')
// process.end(1)
throw err
}
} }
} }
@@ -129,6 +96,7 @@ class Admin {
}) })
await _this.User.deleteOne({ email: 'system@system.com' }) await _this.User.deleteOne({ email: 'system@system.com' })
return true
} catch (err) { } catch (err) {
console.log('Error in admin.js/deleteExistingSystemUser()') console.log('Error in admin.js/deleteExistingSystemUser()')
throw err throw err
@@ -152,7 +120,7 @@ class Admin {
Accept: 'application/json' Accept: 'application/json'
}, },
data: { data: {
email: 'system@system.com', email: existingUser.email,
password: existingUser.password password: existingUser.password
} }
} }
+12 -1
View File
@@ -10,6 +10,8 @@ import UserRESTControllerLib from './controller.js'
import Validators from '../middleware/validators.js' import Validators from '../middleware/validators.js'
import config from '../../../../config/index.js'
let _this let _this
class UserRouter { class UserRouter {
@@ -34,6 +36,7 @@ class UserRouter {
} }
// Encapsulate dependencies. // Encapsulate dependencies.
this.config = config
this.userRESTController = new UserRESTControllerLib(dependencies) this.userRESTController = new UserRESTControllerLib(dependencies)
this.validators = new Validators() this.validators = new Validators()
@@ -52,7 +55,7 @@ class UserRouter {
} }
// Define the routes and attach the controller. // Define the routes and attach the controller.
this.router.post('/', this.userRESTController.createUser) this.router.post('/', this.createUser)
this.router.get('/', this.getAll) this.router.get('/', this.getAll)
this.router.get('/:id', this.getById) this.router.get('/:id', this.getById)
this.router.put('/:id', this.updateUser) this.router.put('/:id', this.updateUser)
@@ -63,6 +66,14 @@ class UserRouter {
app.use(this.router.allowedMethods()) app.use(this.router.allowedMethods())
} }
async createUser (ctx, next) {
if (process.env.DISABLE_NEW_ACCOUNTS) {
await _this.validators.ensureAdmin(ctx, next)
}
await _this.userRESTController.createUser(ctx, next)
return true
}
async getAll (ctx, next) { async getAll (ctx, next) {
await _this.validators.ensureUser(ctx, next) await _this.validators.ensureUser(ctx, next)
await _this.userRESTController.getUsers(ctx, next) await _this.userRESTController.getUsers(ctx, next)
+53
View File
@@ -174,6 +174,59 @@ if (!config.noMongo) {
assert.property(result.data, 'token', 'Token property exists.') assert.property(result.data, 'token', 'Token property exists.')
assert.equal(result.data.user.type, 'user') assert.equal(result.data.user.type, 'user')
}) })
it('should reject signup when DISABLE_NEW_ACCOUNTS is true', async () => {
try {
process.env.DISABLE_NEW_ACCOUNTS = true
const options = {
method: 'POST',
url: `${LOCALHOST}/users`,
data: {
email: 'test2@test.com',
password: 'supersecretpassword',
name: 'test3'
}
}
await axios(options)
assert(false, 'Unexpected result')
} catch (err) {
assert(err.response.status === 401, 'Error code 401 expected.')
}
})
it('admin can create a user when DISABLE_NEW_ACCOUNTS is true', async () => {
process.env.DISABLE_NEW_ACCOUNTS = true
const options = {
method: 'post',
url: `${LOCALHOST}/users`,
headers: {
Authorization: `Bearer ${context.adminJWT}`
},
data: {
user: {
email: 'fromAdmin@test.com',
password: 'supersecretpassword',
name: 'test3'
}
}
}
const result = await axios(options)
context.user = result.data.user
context.token = result.data.token
assert(result.status === 200, 'Status Code 200 expected.')
assert(
result.data.user.email === 'fromAdmin@test.com',
'Email of test expected'
)
assert(
result.data.user.password === undefined,
'Password expected to be omited'
)
assert.property(result.data, 'token', 'Token property exists.')
assert.equal(result.data.user.type, 'user')
})
}) })
describe('GET /users', () => { describe('GET /users', () => {
+43 -28
View File
@@ -62,6 +62,7 @@ describe('Admin', () => {
describe('createSystemUser()', () => { describe('createSystemUser()', () => {
it('should create admin', async () => { it('should create admin', async () => {
try { try {
await uut.deleteExistingSystemUser()
const result = await uut.createSystemUser() const result = await uut.createSystemUser()
assert.property(result, 'email') assert.property(result, 'email')
@@ -72,44 +73,58 @@ describe('Admin', () => {
assert(false, 'Unexpected result') assert(false, 'Unexpected result')
} }
}) })
it('should update admin password', async () => {
it('should handle axios error', async () => {
try { try {
const error1 = new Error('test error') uut.config.adminPassword = 'newpassword'
error1.response = {
status: 422
}
const error2 = new Error('test error')
error1.response = {
status: 500
}
// The loginAdmin() function in some use cases is recursive
// after handling the 422 error, it gets called again
sandbox
.stub(uut.axios, 'request')
.onFirstCall()
.throws(error1)
.onSecondCall()
.throws(error2)
await uut.createSystemUser() const fakeUser = {
password: 'oldpassword',
save: () => { return 'token' },
generateToken: () => { return 'token' }
}
sandbox.stub(uut.User, 'findOne').resolves(fakeUser)
const result = await uut.createSystemUser()
assert.property(result, 'email')
assert.property(result, 'password')
assert.property(result, 'id')
assert.property(result, 'token')
assert.equal(fakeUser.password, 'newpassword', 'password should be updated')
} catch (err) {
console.log(err)
assert(false, 'Unexpected result') assert(false, 'Unexpected result')
}
})
it('should handle error', async () => {
try {
sandbox.stub(uut.User, 'findOne').throws(new Error('test error'))
await uut.createSystemUser()
assert.fail('Unexpected result')
} catch (err) { } catch (err) {
assert.include(err.message, 'test error') assert.include(err.message, 'test error')
} }
}) })
})
it('should handle errors when remove user', async () => { describe('deleteExistingSystemUser()', () => {
it('should delete admin', async () => {
try { try {
const error1 = new Error('test error') sandbox.stub(uut.User, 'deleteOne').resolves(true)
error1.response = { const result = await uut.deleteExistingSystemUser()
status: 422 assert.isTrue(result)
} } catch (err) {
sandbox.stub(uut.axios, 'request').throws(error1)
sandbox.stub(uut.User, 'deleteOne').throws(new Error('test error'))
await uut.createSystemUser()
assert(false, 'Unexpected result') assert(false, 'Unexpected result')
}
})
it('should handle error when deleting admin', async () => {
try {
sandbox.stub(uut.User, 'deleteOne').throws(new Error('test error'))
await uut.deleteExistingSystemUser()
assert.fail('Unexpected result')
} catch (err) { } catch (err) {
assert.include(err.message, 'test error') assert.include(err.message, 'test error')
} }
+3 -4
View File
@@ -1,8 +1,9 @@
import mongoose from 'mongoose' import mongoose from 'mongoose'
import config from '../../config/index.js' import config from '../../config/index.js'
import User from '../../src/adapters/localdb/models/users.js'
const EMAIL = 'test@test.com' const EMAIL = process.env.EMAIL || 'test@test3.com'
const PASSWORD = 'pass' const PASSWORD = process.env.PASSWORD || 'pass'
async function addUser () { async function addUser () {
// Connect to the Mongo Database. // Connect to the Mongo Database.
@@ -13,8 +14,6 @@ async function addUser () {
{ useNewUrlParser: true, useUnifiedTopology: true } { useNewUrlParser: true, useUnifiedTopology: true }
) )
const User = require('../../src/models/users')
const userData = { const userData = {
email: EMAIL, email: EMAIL,
password: PASSWORD password: PASSWORD