User Email and email validation API (#10160)

* feat: wip user emails api

* fix: allow admins with manage-users access to email confirmation api as well

* fix: wrong route path

* docs: openapi spec
This commit is contained in:
Julian Lam
2022-01-21 11:20:39 -05:00
committed by GitHub
parent 868bff302c
commit d098e26f82
6 changed files with 150 additions and 0 deletions

View File

@@ -78,6 +78,12 @@ paths:
$ref: 'write/users/uid/invites.yaml'
/users/{uid}/invites/groups:
$ref: 'write/users/uid/invites/groups.yaml'
/users/{uid}/emails:
$ref: 'write/users/uid/emails.yaml'
/users/{uid}/emails/{email}:
$ref: 'write/users/uid/emails/email.yaml'
/users/{uid}/emails/{email}/confirm:
$ref: 'write/users/uid/emails/email/confirm.yaml'
/groups/:
$ref: 'write/groups.yaml'
/groups/{slug}:

View File

@@ -0,0 +1,33 @@
get:
tags:
- users
summary: get user emails
description: |
This operation lists all emails associated with the user.
This route is accessible to all users if the target user has elected to show their email publicly. Otherwise, it is only accessible to privileged users, or if the calling user is the same as the target user.
parameters:
- in: path
required: true
name: uid
schema:
type: number
description: A valid user id
example: 1
responses:
'200':
description: user emails successfully listed
content:
application/json:
schema:
type: object
properties:
status:
$ref: ../../../components/schemas/Status.yaml#/Status
response:
type: object
properties:
emails:
type: array
items:
type: string
description: An email address

View File

@@ -0,0 +1,25 @@
get:
tags:
- users
summary: get user's email data
description: |
This operation lists the data associated with a single email.
This route is accessible to all users if the target user has elected to show their email publicly. Otherwise, it is only accessible to privileged users, or if the calling user is the same as the target user.
parameters:
- in: path
required: true
name: uid
schema:
type: number
description: A valid user id
example: 1
- in: path
required: true
name: email
schema:
type: string
description: A valid email address
example: test@example.org
responses:
'204':
description: user's email data successfully retrieved

View File

@@ -0,0 +1,34 @@
post:
tags:
- users
summary: validate a user's email address
description: |
Marks the passed-in user's email as confirmed.
This route is only accessible to administrators with the `admin:users` privilege (or superadmins)
parameters:
- in: path
required: true
name: uid
schema:
type: number
description: A valid user id
example: 1
- in: path
required: true
name: email
schema:
type: string
description: A valid email address
example: test@example.org
responses:
'200':
description: successfully confirmed a user email
content:
application/json:
schema:
type: object
properties:
status:
$ref: ../../../../../components/schemas/Status.yaml#/Status
response:
type: object

View File

@@ -233,3 +233,51 @@ Users.getInviteGroups = async function (req, res) {
const userInviteGroups = await groups.getUserInviteGroups(req.params.uid);
return helpers.formatApiResponse(200, res, userInviteGroups);
};
Users.listEmails = async (req, res) => {
const [isPrivileged, { showemail }] = await Promise.all([
user.isPrivileged(req.uid),
user.getSettings(req.params.uid),
]);
const isSelf = req.uid === parseInt(req.params.uid, 10);
if (isSelf || isPrivileged || showemail) {
const emails = await db.getSortedSetRangeByScore('email:uid', 0, 500, req.params.uid, req.params.uid);
helpers.formatApiResponse(200, res, { emails });
} else {
helpers.formatApiResponse(204, res);
}
};
Users.getEmail = async (req, res) => {
const [isPrivileged, { showemail }, exists] = await Promise.all([
user.isPrivileged(req.uid),
user.getSettings(req.params.uid),
db.isSortedSetMember('email:uid', req.params.email.toLowerCase()),
]);
const isSelf = req.uid === parseInt(req.params.uid, 10);
if (exists && (isSelf || isPrivileged || showemail)) {
helpers.formatApiResponse(204, res);
} else {
helpers.formatApiResponse(404, res);
}
};
Users.confirmEmail = async (req, res) => {
const [exists, canManage] = await Promise.all([
db.isSortedSetMember('email:uid', req.params.email.toLowerCase()),
privileges.admin.can('admin:users', req.uid),
]);
if (!canManage) {
helpers.notAllowed(req, res);
}
if (exists) {
await user.email.confirmByUid(req.params.uid);
helpers.formatApiResponse(200, res);
} else {
helpers.formatApiResponse(404, res);
}
};

View File

@@ -44,6 +44,10 @@ function authenticatedRoutes() {
setupApiRoute(router, 'post', '/:uid/invites', middlewares, controllers.write.users.invite);
setupApiRoute(router, 'get', '/:uid/invites/groups', [...middlewares, middleware.assert.user], controllers.write.users.getInviteGroups);
setupApiRoute(router, 'get', '/:uid/emails', [...middlewares, middleware.assert.user], controllers.write.users.listEmails);
setupApiRoute(router, 'get', '/:uid/emails/:email', [...middlewares, middleware.assert.user], controllers.write.users.getEmail);
setupApiRoute(router, 'post', '/:uid/emails/:email/confirm', [...middlewares, middleware.assert.user], controllers.write.users.confirmEmail);
// Shorthand route to access user routes by userslug
router.all('/+bySlug/:userslug*?', [], controllers.write.users.redirectBySlug);
}