Skip to content

Commit

Permalink
Adds resend invite button to user admin page.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
neomorphic committed May 10, 2022
1 parent 9661b49 commit 3c68672
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 21 deletions.
1 change: 1 addition & 0 deletions admin_api_stack/lib/admin_lambda.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
18 changes: 17 additions & 1 deletion admin_api_stack/user_list_resources/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const {
addUserToGroup,
removeUser,
removeUserFromGroup,
resetUserPassword,
confirmUserSignUp,
disableUser,
enableUser,
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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');
Expand Down
67 changes: 49 additions & 18 deletions admin_api_stack/user_list_resources/cognitoActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -482,6 +512,7 @@ module.exports = {
addUser,
addUserToGroup,
removeUser,
resetUserPassword,
removeUserFromGroup,
confirmUserSignUp,
disableUser,
Expand Down
2 changes: 1 addition & 1 deletion vpc_stack/src/jacs/install-jacs-stack.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
8 changes: 7 additions & 1 deletion website/src/components/AdminDataTable.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down Expand Up @@ -35,7 +36,12 @@ export default function AdminDataTable({ loading, dataSource }) {
title: "Action",
key: "action",
render: (text, record) => {
return <DeleteUser username={record.Username} />;
return (
<>
<DeleteUser username={record.Username} />
<ResetUser username={record.Username} status={record.UserStatus} />
</>
);
},
},
];
Expand Down
57 changes: 57 additions & 0 deletions website/src/components/ResetUser.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<Popconfirm
title="Are you sure?"
okText="Yes"
cancelText="No"
onConfirm={resendInvite}
icon={<QuestionCircleOutlined style={{ color: "red" }} />}
>
<Button>Resend Invite</Button>
</Popconfirm>
);
}
return null;
}

ResetUser.propTypes = {
username: PropTypes.string.isRequired,
status: PropTypes.string.isRequired,
};

0 comments on commit 3c68672

Please sign in to comment.