Skip to content
Snippets Groups Projects
Verified Commit 79e6e17a authored by Daniel Göbel's avatar Daniel Göbel
Browse files

Add button to resend invitation emails

parent b35a8fb3
No related branches found
No related tags found
1 merge request!118Resolve "Handle invitation links"
This commit is part of merge request !118. Comments created here will be created in the context of that merge request.
......@@ -20,5 +20,9 @@ export type UserOutExtended = {
* Lifesicence ID of the user
*/
lifescience_id?: (string | null);
/**
* Timestamp when the invitation token was created as UNIX timestamp
*/
invitation_token_created_at?: (number | null);
};
......@@ -8,14 +8,16 @@ import { OpenAPI } from '../core/OpenAPI';
import { request as __request } from '../core/request';
export class AuthService {
/**
* Redirect to LifeScience OIDC Login
* Kickstart the login flow
* Redirect route to OIDC provider to kickstart the login process.
* @param invitationToken Unique token to validate an invitation
* @param provider The OIDC provider to use for login
* @param next Will be appended to redirect response in the callback route as URL query parameter `next_path`
* @param next Will be appended to redirect response in the callback route as URL query parameter `next`
* @returns void
* @throws ApiError
*/
public static authLogin(
invitationToken?: string,
provider?: OIDCProvider,
next?: string,
): CancelablePromise<void> {
......@@ -23,6 +25,7 @@ export class AuthService {
method: 'GET',
url: '/auth/login',
query: {
'invitation_token': invitationToken,
'provider': provider,
'next': next,
},
......@@ -65,6 +68,7 @@ export class AuthService {
}
/**
* Logout
* Logout the user from the system by deleting the bearer cookie.
* @returns void
* @throws ApiError
*/
......
......@@ -13,7 +13,7 @@ import { request as __request } from '../core/request';
export class UserService {
/**
* Create User
* Create a new user in the system and notify him. The smtp MUST be the same as the one saved by the OIDC provider.
* Create a new user in the system and notify him.
*
* Permission `user:create` required.
* @param requestBody
......@@ -172,4 +172,31 @@ export class UserService {
},
});
}
/**
* Resend Invitation
* Resend the invitation link for an user that has an open invitation.
*
* Permission `user:create` required.
* @param uid UID of a user
* @returns UserOutExtended Successful Response
* @throws ApiError
*/
public static userResendInvitation(
uid: string,
): CancelablePromise<UserOutExtended> {
return __request(OpenAPI, {
method: 'PATCH',
url: '/users/{uid}/invitation',
path: {
'uid': uid,
},
errors: {
400: `Error decoding JWT Token`,
401: `Not Authenticated`,
403: `Not Authorized`,
404: `Entity not Found`,
422: `Validation Error`,
},
});
}
}
......@@ -45,7 +45,7 @@ function createUser() {
formState.loading = true;
formState.registeredUserName = formState.user.display_name;
userRepository
.createUser(formState.user)
.inviteUser(formState.user)
.then((user) => {
emit("user-created", user);
formState.validated = false;
......@@ -76,11 +76,11 @@ onMounted(() => {
<template>
<bootstrap-toast toast-id="create-user-success-toast">
Successfully registered user {{ formState.registeredUserName }}
Successfully invited user {{ formState.registeredUserName }}
</bootstrap-toast>
<bootstrap-toast toast-id="create-user-error-toast" color-class="danger">
<template #default
>Couldn't regsiter user
>Couldn't invite user
{{ formState.registeredUserName }}
</template>
<template #body>Error: {{ formState.errorMessage }}</template>
......@@ -90,7 +90,7 @@ onMounted(() => {
:static-backdrop="true"
modal-label="Create user"
>
<template #header>Register a user</template>
<template #header>Invite a user</template>
<template #body>
<form
id="create-user-form"
......
......@@ -113,12 +113,18 @@ export const useUserStore = defineStore({
roles: roles,
});
},
createUser(userIn: UserIn): Promise<UserOutExtended> {
inviteUser(userIn: UserIn): Promise<UserOutExtended> {
return UserService.userCreateUser(userIn).then((user) => {
useNameStore().addNameToMapping(user.uid, user.display_name);
return user;
});
},
resendInvitationEmail(uid: string): Promise<UserOutExtended> {
return UserService.userResendInvitation(uid).then((user) => {
useNameStore().addNameToMapping(user.uid, user.display_name);
return user;
});
},
searchUser(searchString: string): Promise<UserOut[]> {
return UserService.userSearchUsers(searchString).then((users) => {
const nameStore = useNameStore();
......
......@@ -82,7 +82,7 @@ onMounted(() => {
<div v-else class="card-header">Login</div>
<div class="card-body">
<p v-if="invitationToken" class="card-text text-secondary">
Connect your CloWM account with your LifeScience account
Connect your newly created CloWM account with your LifeScience account
</p>
<p v-else class="card-text text-secondary">
Login to this service with LifeScience
......
......@@ -9,7 +9,8 @@ import CreateUserModal from "@/components/admin/CreateUserModal.vue";
const userRepository = useUserStore();
type RoleList = RoleEnum[];
let successToast: Toast | undefined;
let successRoleToast: Toast | undefined;
let successInvitationToast: Toast | undefined;
let errorToast: Toast | undefined;
const userState = reactive<{
......@@ -62,7 +63,7 @@ function saveUserRoles(index: number) {
.then((user) => {
userState.users[index] = user;
userState.editUserRoles[index] = false;
successToast?.show();
successRoleToast?.show();
})
.catch((err) => {
userState.errorMessage = err.body?.detail;
......@@ -80,13 +81,29 @@ function resetUserRoles(index: number) {
);
}
function resendInvitationEmail(uid: string) {
userState.loading = true;
userRepository
.resendInvitationEmail(uid)
.then(() => {
successInvitationToast?.show();
})
.finally(() => {
userState.loading = false;
});
}
onMounted(() => {
successToast = new Toast("#change-role-success-toast");
successRoleToast = new Toast("#change-role-success-toast");
successInvitationToast = new Toast("#resend-invitation-success-toast");
errorToast = new Toast("#change-role-error-toast");
});
</script>
<template>
<bootstrap-toast toast-id="resend-invitation-success-toast">
Successfully resend invitation email
</bootstrap-toast>
<bootstrap-toast toast-id="change-role-success-toast">
Successfully change roles of user {{ userState.editedUser?.display_name }}
</bootstrap-toast>
......@@ -108,7 +125,7 @@ onMounted(() => {
data-bs-toggle="modal"
data-bs-target="#create-user-modal"
>
Register User
Invite User
</button>
</div>
<form @submit.prevent="searchUsers" id="admin-user-search-form">
......@@ -234,13 +251,35 @@ onMounted(() => {
</div>
</td>
<td class="text-end" v-else>
<button
type="button"
class="btn btn-sm btn-secondary"
@click="userState.editUserRoles[index] = true"
>
Edit Roles
</button>
<div class="btn-group">
<button
type="button"
class="btn btn-sm btn-secondary"
@click="userState.editUserRoles[index] = true"
>
Edit Roles
</button>
<button
v-if="user.invitation_token_created_at"
type="button"
class="btn btn-secondary btn-sm dropdown-toggle dropdown-toggle-split"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<span class="visually-hidden">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
<li>
<a
class="dropdown-item"
:class="{ disabled: userState.loading }"
:aria-disabled="userState.loading"
@click="resendInvitationEmail(user.uid)"
>Resend invitation email</a
>
</li>
</ul>
</div>
</td>
</tr>
</tbody>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment