diff --git a/sites/backend/openapi/users.mjs b/sites/backend/openapi/users.mjs index d109320b24f..60dc5fcf18a 100644 --- a/sites/backend/openapi/users.mjs +++ b/sites/backend/openapi/users.mjs @@ -514,3 +514,52 @@ paths['/users/{username}'] = { }, }, } + +// Check whether a username is available +const checkUsername = "Little Miss Can't Be Wrong" +paths['/available/username'] = { + post: { + tags: ['Users'], + summary: `Checks whether a username is available`, + description: + 'This allows a background check to see whether a username is available during signup', + requestBody: { + required: true, + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + username: { + type: 'string', + description: 'The username to check for availability', + example: checkUsername, + }, + }, + }, + }, + }, + }, + responses: { + 200: { + description: + '**Success - Username is NOT available**\n\n' + + 'Status code `200` indicates that the username exists.', + ...jsonResponse({ + result: 'success', + username: checkUsername, + available: false, + }), + }, + 401: response.status['401'], + 403: { + ...response.status['403'], + description: + response.status['403'].description + + errorExamples(['accountStatusLacking', 'insufficientAccessLevel']), + }, + 404: response.status['404'], + 500: response.status['500'], + }, + }, +} diff --git a/sites/backend/src/controllers/users.mjs b/sites/backend/src/controllers/users.mjs index b1880f0ef44..872e92a640b 100644 --- a/sites/backend/src/controllers/users.mjs +++ b/sites/backend/src/controllers/users.mjs @@ -78,3 +78,23 @@ UsersController.prototype.updateMfa = async (req, res, tools) => { return User.sendResponse(res) } + +/* + * Checks whether a submitted username is available + * + * See: https://freesewing.dev/reference/backend/api + */ +UsersController.prototype.isUsernameAvailable = async (req, res, tools) => { + const User = new UserModel(tools) + await User.find(req.body) + + if (User.exists) + User.setResponse(200, false, { + result: 'success', + username: req.body?.username, + available: false, + }) + else User.setResponse(404) + + return User.sendResponse(res) +} diff --git a/sites/backend/src/models/apikey.mjs b/sites/backend/src/models/apikey.mjs index d3c29ba45a7..e3e36614d6c 100644 --- a/sites/backend/src/models/apikey.mjs +++ b/sites/backend/src/models/apikey.mjs @@ -52,7 +52,7 @@ ApikeyModel.prototype.guardedRead = async function ({ params, user }) { if (user.iss && user.status < 1) return this.setResponse(403, 'accountStatusLacking') await this.unguardedRead({ id: params.id }) - if (!this.record) return this.setResponse(404, 'apikeyNotFound') + if (!this.record) return this.setResponse(404) if (this.record.userId !== user.uid) { // Not own key - only admin can do that @@ -76,7 +76,7 @@ ApikeyModel.prototype.guardedDelete = async function ({ params, user }) { if (user.iss && user.status < 1) return this.setResponse(403, 'accountStatusLacking') await this.unguardedRead({ id: params.id }) - if (!this.record) return this.setResponse(404, 'apikeyNotFound') + if (!this.record) return this.setResponse(404) if (this.record.userId !== user.uid) { // Not own key - only admin can do that diff --git a/sites/backend/src/models/user.mjs b/sites/backend/src/models/user.mjs index 40d49ae2ee9..c583d43d205 100644 --- a/sites/backend/src/models/user.mjs +++ b/sites/backend/src/models/user.mjs @@ -265,7 +265,7 @@ UserModel.prototype.passwordLogin = async function (req) { * Confirms a user account */ UserModel.prototype.confirm = async function ({ body, params }) { - if (!params.id) return this.setResponse(404, 'confirmationIdMissing') + if (!params.id) return this.setResponse(404) if (Object.keys(body) < 1) return this.setResponse(400, 'postBodyMissing') if (!body.consent || typeof body.consent !== 'number' || body.consent < 1) return this.setResponse(400, 'consentRequired') @@ -275,20 +275,18 @@ UserModel.prototype.confirm = async function ({ body, params }) { if (!this.Confirmation.exists) { log.warn(err, `Could not find confirmation id ${params.id}`) - return this.setResponse(404, 'failedToFindConfirmationId') + return this.setResponse(404) } if (this.Confirmation.record.type !== 'signup') { log.warn(err, `Confirmation mismatch; ${params.id} is not a signup id`) - return this.setResponse(404, 'confirmationIdTypeMismatch') + return this.setResponse(404) } if (this.error) return this const data = this.Confirmation.clear.data - if (data.ehash !== this.Confirmation.record.user.ehash) - return this.setResponse(404, 'confirmationEhashMismatch') - if (data.id !== this.Confirmation.record.user.id) - return this.setResponse(404, 'confirmationUserIdMismatch') + if (data.ehash !== this.Confirmation.record.user.ehash) return this.setResponse(404) + if (data.id !== this.Confirmation.record.user.id) return this.setResponse(404) // Load user await this.read({ id: this.Confirmation.record.user.id }) @@ -404,12 +402,12 @@ UserModel.prototype.guardedUpdate = async function ({ body, user }) { if (!this.Confirmation.exists) { log.warn(err, `Could not find confirmation id ${params.id}`) - return this.setResponse(404, 'failedToFindConfirmationId') + return this.setResponse(404) } if (this.Confirmation.record.type !== 'emailchange') { log.warn(err, `Confirmation mismatch; ${params.id} is not an emailchange id`) - return this.setResponse(404, 'confirmationIdTypeMismatch') + return this.setResponse(404) } const data = this.Confirmation.clear.data @@ -570,6 +568,7 @@ UserModel.prototype.setResponse = function (status = 200, error = false, data = this.response.body.result = 'error' this.error = true } else this.error = false + if (status === 404) this.response.body = null return this.setExists() } diff --git a/sites/backend/src/routes/users.mjs b/sites/backend/src/routes/users.mjs index ebc85c418b5..c5081fcd988 100644 --- a/sites/backend/src/routes/users.mjs +++ b/sites/backend/src/routes/users.mjs @@ -40,6 +40,10 @@ export function usersRoutes(tools) { app.post('/account/mfa/key', passport.authenticate(...bsc), (req, res) => Users.updateMfa(req, res, tools) ) + + // Check whether a username is available + app.post('/available/username', (req, res) => Users.isUsernameAvailable(req, res, tools)) + /* // Remove account @@ -95,11 +99,5 @@ export function usersRoutes(tools) { (req, res) => User.readProfile(req, res, params) ) - // Check whether a username is available - app.post( - '/available/username', - passport.authenticate('jwt', { session: false }), - (req, res) => User.isUsernameAvailable(req, res, params) - ) */ } diff --git a/sites/backend/tests/user.mjs b/sites/backend/tests/user.mjs index 00f353a39e6..b4acc5003e8 100644 --- a/sites/backend/tests/user.mjs +++ b/sites/backend/tests/user.mjs @@ -258,4 +258,36 @@ export const userTests = async (chai, config, expect, store) => { }) }) }) + + describe(`${store.icon('user')} Check for available usernames`, () => { + it(`${store.icon('user')} Should find a username is available`, (done) => { + chai + .request(config.api) + .post(`/available/username`) + .send({ + username: 'haochi', + }) + .end((err, res) => { + expect(err === null).to.equal(true) + expect(res.status).to.equal(404) + done() + }) + }) + it(`${store.icon('user')} Should find a username is not available`, (done) => { + chai + .request(config.api) + .post(`/available/username`) + .send({ + username: store.account.username, + }) + .end((err, res) => { + expect(err === null).to.equal(true) + expect(res.status).to.equal(200) + expect(res.body.result).to.equal('success') + expect(res.body.available).to.equal(false) + expect(res.body.username).to.equal(store.account.username) + done() + }) + }) + }) }