feat: Add manual login option and password reset link for SSO (#6328)

* consolidate IUserSettings in workflow and add allowSSOManualLogin

* add pw reset link to owners ui
This commit is contained in:
Michael Auerswald
2023-05-30 12:52:02 +02:00
committed by GitHub
parent 8f0ff460b1
commit 77e3f1551d
15 changed files with 215 additions and 35 deletions

View File

@@ -33,6 +33,7 @@ import type {
IN8nUISettings,
IUserManagementSettings,
WorkflowSettings,
IUserSettings,
} from 'n8n-workflow';
import type { SignInType } from './constants';
import type {
@@ -561,12 +562,7 @@ export interface IUserResponse {
personalizationAnswers?: IPersonalizationSurveyVersions | null;
isPending: boolean;
signInType?: SignInType;
settings?: {
isOnboarded?: boolean;
showUserActivationSurvey?: boolean;
firstSuccessfulWorkflowId?: string;
userActivated?: boolean;
};
settings?: IUserSettings;
}
export interface CurrentUserResponse extends IUserResponse {

View File

@@ -105,7 +105,15 @@ export async function updateCurrentUserSettings(
context: IRestApiContext,
settings: IUserResponse['settings'],
): Promise<IUserResponse['settings']> {
return makeRestApiRequest(context, 'PATCH', '/me/settings', settings);
return makeRestApiRequest(context, 'PATCH', '/me/settings', settings as IDataObject);
}
export async function updateOtherUserSettings(
context: IRestApiContext,
userId: string,
settings: IUserResponse['settings'],
): Promise<IUserResponse['settings']> {
return makeRestApiRequest(context, 'PATCH', `/users/${userId}/settings`, settings as IDataObject);
}
export async function updateCurrentUserPassword(
@@ -144,6 +152,13 @@ export async function getInviteLink(
return makeRestApiRequest(context, 'GET', `/users/${id}/invite-link`);
}
export async function getPasswordResetLink(
context: IRestApiContext,
{ id }: { id: string },
): Promise<{ link: string }> {
return makeRestApiRequest(context, 'GET', `/users/${id}/password-reset-link`);
}
export async function submitPersonalizationSurvey(
context: IRestApiContext,
params: IPersonalizationLatestVersion,

View File

@@ -1169,6 +1169,9 @@
"settings.users.actions.delete": "Delete User",
"settings.users.actions.reinvite": "Resend Invite",
"settings.users.actions.copyInviteLink": "Copy Invite Link",
"settings.users.actions.copyPasswordResetLink": "Copy Password Reset Link",
"settings.users.actions.allowSSOManualLogin": "Allow Manual Login",
"settings.users.actions.disallowSSOManualLogin": "Disallow Manual Login",
"settings.users.deleteWorkflowsAndCredentials": "Delete their workflows and credentials",
"settings.users.emailInvitesSent": "An invite email was sent to {emails}",
"settings.users.emailInvitesSentError": "Could not invite {emails}",
@@ -1187,6 +1190,12 @@
"settings.users.inviteXUser.inviteUrl": "Create {count} invite links",
"settings.users.inviteUrlCreated": "Invite link copied to clipboard",
"settings.users.inviteUrlCreated.message": "Send the invite link to your invitee for activation",
"settings.users.passwordResetUrlCreated": "Password reset link copied to clipboard",
"settings.users.passwordResetUrlCreated.message": "Send the reset link to your user for them to reset their password",
"settings.users.allowSSOManualLogin": "Manual Login Allowed",
"settings.users.allowSSOManualLogin.message": "User can now login manually and through SSO",
"settings.users.disallowSSOManualLogin": "Manual Login Disallowed",
"settings.users.disallowSSOManualLogin.message": "User must now login through SSO only",
"settings.users.multipleInviteUrlsCreated": "Invite links created",
"settings.users.multipleInviteUrlsCreated.message": "Send the invite links to your invitees for activation",
"settings.users.newEmailsToInvite": "New User Email Addresses",

View File

@@ -2,6 +2,7 @@ import {
changePassword,
deleteUser,
getInviteLink,
getPasswordResetLink,
getUsers,
inviteUsers,
login,
@@ -17,6 +18,7 @@ import {
updateCurrentUser,
updateCurrentUserPassword,
updateCurrentUserSettings,
updateOtherUserSettings,
validatePasswordToken,
validateSignupToken,
} from '@/api/users';
@@ -251,6 +253,19 @@ export const useUsersStore = defineStore(STORES.USERS, {
this.addUsers([this.currentUser]);
}
},
async updateOtherUserSettings(
userId: string,
settings: IUserResponse['settings'],
): Promise<void> {
const rootStore = useRootStore();
const updatedSettings = await updateOtherUserSettings(
rootStore.getRestApiContext,
userId,
settings,
);
this.users[userId].settings = updatedSettings;
this.addUsers([this.users[userId]]);
},
async updateCurrentUserPassword({
password,
currentPassword,
@@ -288,6 +303,10 @@ export const useUsersStore = defineStore(STORES.USERS, {
const rootStore = useRootStore();
return getInviteLink(rootStore.getRestApiContext, params);
},
async getUserPasswordResetLink(params: { id: string }): Promise<{ link: string }> {
const rootStore = useRootStore();
return getPasswordResetLink(rootStore.getRestApiContext, params);
},
async submitPersonalizationSurvey(results: IPersonalizationLatestVersion): Promise<void> {
const rootStore = useRootStore();
await submitPersonalizationSurvey(rootStore.getRestApiContext, results);

View File

@@ -50,9 +50,13 @@
:actions="usersListActions"
:users="usersStore.allUsers"
:currentUserId="usersStore.currentUserId"
:isSamlLoginEnabled="ssoStore.isSamlLoginEnabled"
@delete="onDelete"
@reinvite="onReinvite"
@copyInviteLink="onCopyInviteLink"
@copyPasswordResetLink="onCopyPasswordResetLink"
@allowSSOManualLogin="onAllowSSOManualLogin"
@disallowSSOManualLogin="onDisallowSSOManualLogin"
/>
</div>
</div>
@@ -106,6 +110,22 @@ export default defineComponent({
label: this.$locale.baseText('settings.users.actions.delete'),
value: 'delete',
},
{
label: this.$locale.baseText('settings.users.actions.copyPasswordResetLink'),
value: 'copyPasswordResetLink',
},
{
label: this.$locale.baseText('settings.users.actions.allowSSOManualLogin'),
value: 'allowSSOManualLogin',
guard: (user) =>
this.settingsStore.isSamlLoginEnabled && !user.settings?.allowSSOManualLogin,
},
{
label: this.$locale.baseText('settings.users.actions.disallowSSOManualLogin'),
value: 'disallowSSOManualLogin',
guard: (user) =>
this.settingsStore.isSamlLoginEnabled && user.settings?.allowSSOManualLogin === true,
},
];
},
},
@@ -152,6 +172,44 @@ export default defineComponent({
});
}
},
async onCopyPasswordResetLink(userId: string) {
const user = this.usersStore.getUserById(userId) as IUser | null;
if (user) {
const url = await this.usersStore.getUserPasswordResetLink(user);
this.copyToClipboard(url.link);
this.showToast({
type: 'success',
title: this.$locale.baseText('settings.users.passwordResetUrlCreated'),
message: this.$locale.baseText('settings.users.passwordResetUrlCreated.message'),
});
}
},
async onAllowSSOManualLogin(userId: string) {
const user = this.usersStore.getUserById(userId) as IUser | null;
if (user?.settings) {
user.settings.allowSSOManualLogin = true;
await this.usersStore.updateOtherUserSettings(userId, user.settings);
this.showToast({
type: 'success',
title: this.$locale.baseText('settings.users.allowSSOManualLogin'),
message: this.$locale.baseText('settings.users.allowSSOManualLogin.message'),
});
}
},
async onDisallowSSOManualLogin(userId: string) {
const user = this.usersStore.getUserById(userId) as IUser | null;
if (user?.settings) {
user.settings.allowSSOManualLogin = false;
await this.usersStore.updateOtherUserSettings(userId, user.settings);
this.showToast({
type: 'success',
title: this.$locale.baseText('settings.users.disallowSSOManualLogin'),
message: this.$locale.baseText('settings.users.disallowSSOManualLogin.message'),
});
}
},
goToUpgrade() {
this.uiStore.goToUpgrade('users', 'upgrade-users');
},