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)
// '/ip4/78.46.129.7/tcp/4001/p2p/12D3KooWFQ11GQ5NubsJGhYZ4X3wrAGimLevxfm6HPExCrMYhpSL'
]
],
// END IPFS CONFIGURATION
// Account Configuration
disableNewAccounts: process.env.DISABLE_NEW_ACCOUNTS ? true : false,
// Admin password
adminPassword: process.env.ADMIN_PASSWORD
}
+25 -57
View File
@@ -51,70 +51,37 @@ class Admin {
// used by the Listing Manager and test scripts, in order access private API
// functions.
async createSystemUser () {
// Create the system user.
try {
context.password = _this._randomString(20)
const options = {
method: 'POST',
url: `${LOCALHOST}/users`,
data: {
user: {
const context = {
email: 'system@system.com',
password: context.password,
name: 'admin'
name: 'admin',
password: _this.config.adminPassword || _this._randomString(20),
type: 'admin'
}
// Check if the user already exists
let adminUser = await _this.User.findOne({ email: context.email })
if (adminUser) {
// Update the password
adminUser.password = context.password
} else {
// Create a new admin user
adminUser = new _this.User(context)
}
}
const result = await _this.axios.request(options)
// console.log('admin.data: ', result.data)
// Update context with the new user id and token
context.id = adminUser._id
context.token = await adminUser.generateToken()
context.email = result.data.user.email
context.id = result.data.user._id
context.token = result.data.token
// Save the user
await adminUser.save()
// Get the mongoDB entry
const user = await _this.User.findById(context.id)
// Change the user type to admin
user.type = 'admin'
// 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)
// Write the user data to the JSON file
await _this.jsonFiles.writeJSON(context, JSON_PATH)
return context
} catch (err) {
// Handle existing system user.
if (err.response.status === 422) {
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
}
} catch (error) {
console.log('Error in admin.js/createSystemUser()')
throw error
}
}
@@ -129,6 +96,7 @@ class Admin {
})
await _this.User.deleteOne({ email: 'system@system.com' })
return true
} catch (err) {
console.log('Error in admin.js/deleteExistingSystemUser()')
throw err
@@ -152,7 +120,7 @@ class Admin {
Accept: 'application/json'
},
data: {
email: 'system@system.com',
email: existingUser.email,
password: existingUser.password
}
}
+12 -1
View File
@@ -10,6 +10,8 @@ import UserRESTControllerLib from './controller.js'
import Validators from '../middleware/validators.js'
import config from '../../../../config/index.js'
let _this
class UserRouter {
@@ -34,6 +36,7 @@ class UserRouter {
}
// Encapsulate dependencies.
this.config = config
this.userRESTController = new UserRESTControllerLib(dependencies)
this.validators = new Validators()
@@ -52,7 +55,7 @@ class UserRouter {
}
// 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('/:id', this.getById)
this.router.put('/:id', this.updateUser)
@@ -63,6 +66,14 @@ class UserRouter {
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) {
await _this.validators.ensureUser(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.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', () => {
+43 -28
View File
@@ -62,6 +62,7 @@ describe('Admin', () => {
describe('createSystemUser()', () => {
it('should create admin', async () => {
try {
await uut.deleteExistingSystemUser()
const result = await uut.createSystemUser()
assert.property(result, 'email')
@@ -72,44 +73,58 @@ describe('Admin', () => {
assert(false, 'Unexpected result')
}
})
it('should handle axios error', async () => {
it('should update admin password', async () => {
try {
const error1 = new Error('test error')
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)
uut.config.adminPassword = 'newpassword'
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')
}
})
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) {
assert.include(err.message, 'test error')
}
})
})
it('should handle errors when remove user', async () => {
describe('deleteExistingSystemUser()', () => {
it('should delete admin', async () => {
try {
const error1 = new Error('test error')
error1.response = {
status: 422
}
sandbox.stub(uut.axios, 'request').throws(error1)
sandbox.stub(uut.User, 'deleteOne').throws(new Error('test error'))
await uut.createSystemUser()
sandbox.stub(uut.User, 'deleteOne').resolves(true)
const result = await uut.deleteExistingSystemUser()
assert.isTrue(result)
} catch (err) {
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) {
assert.include(err.message, 'test error')
}
+3 -4
View File
@@ -1,8 +1,9 @@
import mongoose from 'mongoose'
import config from '../../config/index.js'
import User from '../../src/adapters/localdb/models/users.js'
const EMAIL = 'test@test.com'
const PASSWORD = 'pass'
const EMAIL = process.env.EMAIL || 'test@test3.com'
const PASSWORD = process.env.PASSWORD || 'pass'
async function addUser () {
// Connect to the Mongo Database.
@@ -13,8 +14,6 @@ async function addUser () {
{ useNewUrlParser: true, useUnifiedTopology: true }
)
const User = require('../../src/models/users')
const userData = {
email: EMAIL,
password: PASSWORD