From 3c686723ac34ac4d52ca714e2b3e4f4cd3f68448 Mon Sep 17 00:00:00 2001 From: Jody Clements Date: Tue, 10 May 2022 11:23:12 -0400 Subject: [PATCH] Adds resend invite button to user admin page. If a user has not yet changed their password or lost their invite email, this button will reset the time limit to login and send out a new random password. --- admin_api_stack/lib/admin_lambda.ts | 1 + admin_api_stack/user_list_resources/app.js | 18 ++++- .../user_list_resources/cognitoActions.js | 67 ++++++++++++++----- vpc_stack/src/jacs/install-jacs-stack.sh | 2 +- website/src/components/AdminDataTable.jsx | 8 ++- website/src/components/ResetUser.jsx | 57 ++++++++++++++++ 6 files changed, 132 insertions(+), 21 deletions(-) create mode 100644 website/src/components/ResetUser.jsx diff --git a/admin_api_stack/lib/admin_lambda.ts b/admin_api_stack/lib/admin_lambda.ts index 88189e3..a6ea11e 100644 --- a/admin_api_stack/lib/admin_lambda.ts +++ b/admin_api_stack/lib/admin_lambda.ts @@ -149,6 +149,7 @@ export class LambdaService extends Construct { "cognito-idp:AdminEnableUser", "cognito-idp:AdminDisableUser", "cognito-idp:AdminRemoveUserFromGroup", + "cognito-idp:AdminResetUserPassword", "cognito-idp:AdminAddUserToGroup", "cognito-idp:AdminListGroupsForUser", "cognito-idp:AdminGetUser", diff --git a/admin_api_stack/user_list_resources/app.js b/admin_api_stack/user_list_resources/app.js index cae2e4f..73c0d51 100644 --- a/admin_api_stack/user_list_resources/app.js +++ b/admin_api_stack/user_list_resources/app.js @@ -20,6 +20,7 @@ const { addUserToGroup, removeUser, removeUserFromGroup, + resetUserPassword, confirmUserSignUp, disableUser, enableUser, @@ -98,7 +99,7 @@ app.post('/addUser', async (req, res, next) => { try { // fetch the authorized user for contacting the JACS API. const {email: authUser} = req.apiGateway.event.requestContext.authorizer.claims; - const response = await addUser(req.body.username, authUser); + const response = await addUser(req.body.username, authUser, req.body.resend); res.status(200).json(response); } catch (err) { next(err); @@ -136,6 +137,21 @@ app.post('/removeUser', async (req, res, next) => { } }); +app.post('/resetUserPassword', async (req, res, next) => { + if (!req.body.username ) { + const err = new Error('username is required'); + err.statusCode = 400; + return next(err); + } + + try { + const response = await resetUserPassword(req.body.username); + res.status(200).json(response); + } catch (err) { + next(err); + } +}); + app.post('/confirmUserSignUp', async (req, res, next) => { if (!req.body.username) { const err = new Error('username is required'); diff --git a/admin_api_stack/user_list_resources/cognitoActions.js b/admin_api_stack/user_list_resources/cognitoActions.js index 24ed3f5..86f1f14 100644 --- a/admin_api_stack/user_list_resources/cognitoActions.js +++ b/admin_api_stack/user_list_resources/cognitoActions.js @@ -85,7 +85,8 @@ async function getAuthToken(username) { return token; } -async function addUser(username, authUser) { +async function addUser(username, authUser, resend) { + console.log({resend}); console.log(`Attempting to add ${username} to userpool ${userPoolId}`); const passwordParams = { @@ -115,30 +116,38 @@ async function addUser(username, authUser) { } ] }; + + if (resend) { + params.MessageAction = 'RESEND'; + delete params.UserAttributes; + } + await cognitoIdentityServiceProvider .adminCreateUser(params) .promise(); console.log(`Success adding ${username} to userpool ${userPoolId}`); // code to connect to Workstation API and add the user. - const authToken = await getAuthToken(authUser); - await sendRequest( - "/SCSW/JACS2SyncServices/v2/data/user", - "PUT", - { - key: `user:${username}`, - name: username, - fullName: username, - email: username, - password: '', - class: "org.janelia.model.security.User" - }, - authToken - ); + if (!resend) { + const authToken = await getAuthToken(authUser); + await sendRequest( + "/SCSW/JACS2SyncServices/v2/data/user", + "PUT", + { + key: `user:${username}`, + name: username, + fullName: username, + email: username, + password: '', + class: "org.janelia.model.security.User" + }, + authToken + ); - return { - message: `Success adding ${username} to userpool` - }; + return { + message: `Success adding ${username} to userpool` + }; + } } catch (err) { console.log(err); throw err; @@ -199,6 +208,27 @@ async function addUserToGroup(username, groupname, authUser) { } } +async function resetUserPassword(username) { + const params = { + UserPoolId: userPoolId, + Username: username + }; + + console.log(`Attempting to reset password for ${username}`); + try { + await cognitoIdentityServiceProvider + .adminResetUserPassword(params) + .promise(); + console.log(`Reset password for ${username} in userpool ${userPoolId}`); + return { + message: `Reset password for ${username}` + }; + } catch (err) { + console.log(err); + throw err; + } +} + async function removeUser(username) { const params = { UserPoolId: userPoolId, @@ -482,6 +512,7 @@ module.exports = { addUser, addUserToGroup, removeUser, + resetUserPassword, removeUserFromGroup, confirmUserSignUp, disableUser, diff --git a/vpc_stack/src/jacs/install-jacs-stack.sh b/vpc_stack/src/jacs/install-jacs-stack.sh index 3fb0260..9dbc9fb 100644 --- a/vpc_stack/src/jacs/install-jacs-stack.sh +++ b/vpc_stack/src/jacs/install-jacs-stack.sh @@ -259,7 +259,7 @@ function createScheduledJobs() { echo "Create scheduled jobs from $(cat ${DEPLOY_DIR}/local/scheduled_jobs.json)" ./manage.sh mongo \ -tool mongoimport \ - -run-opts "-v ${DEPLOY_DIR}/local:/local" \ + -notty -run-opts "-v ${DEPLOY_DIR}/local:/local" \ --collection jacsScheduledService /local/scheduled_jobs.json } diff --git a/website/src/components/AdminDataTable.jsx b/website/src/components/AdminDataTable.jsx index 2bd765c..34e7ec5 100644 --- a/website/src/components/AdminDataTable.jsx +++ b/website/src/components/AdminDataTable.jsx @@ -4,6 +4,7 @@ import PropTypes from "prop-types"; import AdminStatus from "./AdminStatus"; import ActiveStatus from "./ActiveStatus"; import DeleteUser from "./DeleteUser"; +import ResetUser from "./ResetUser"; export default function AdminDataTable({ loading, dataSource }) { const columns = [ @@ -35,7 +36,12 @@ export default function AdminDataTable({ loading, dataSource }) { title: "Action", key: "action", render: (text, record) => { - return ; + return ( + <> + + + + ); }, }, ]; diff --git a/website/src/components/ResetUser.jsx b/website/src/components/ResetUser.jsx new file mode 100644 index 0000000..97ce827 --- /dev/null +++ b/website/src/components/ResetUser.jsx @@ -0,0 +1,57 @@ +import { Button, Popconfirm, message } from "antd"; +import { QuestionCircleOutlined } from "@ant-design/icons"; +import { PropTypes } from "prop-types"; +import { API } from "aws-amplify"; +import { useQueryClient } from "react-query"; + +export default function ResetUser({ username, status }) { + const queryClient = useQueryClient(); + + function resetUser() { + API.post("AppStreamAPI", "resetUserPassword", { + body: { username }, + }) + .then(() => { + queryClient.invalidateQueries("users"); + message.success(`resetting password for user: ${username}`); + }) + .catch((e) => { + const responseMessage = e?.response?.data?.message || e.message; + message.error(responseMessage); + }); + } + + function resendInvite() { + API.post("AppStreamAPI", "/addUser", { + body: { username, resend: 1 }, + }) + .then(() => { + queryClient.invalidateQueries("users"); + message.success(`resending invite for user: ${username}`); + }) + .catch((e) => { + const responseMessage = e?.response?.data?.message || e.message; + message.error(responseMessage); + }); + } + + if (status === "FORCE_CHANGE_PASSWORD") { + return ( + } + > + + + ); + } + return null; +} + +ResetUser.propTypes = { + username: PropTypes.string.isRequired, + status: PropTypes.string.isRequired, +};