mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-02-10 16:39:17 +00:00
feat: add ability to send login code via email (#457)
Co-authored-by: Kyle Mendell <kmendell@ofkm.us>
This commit is contained in:
@@ -9,8 +9,10 @@
|
||||
import { Separator } from '$lib/components/ui/separator';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import UserService from '$lib/services/user-service';
|
||||
import appConfigStore from '$lib/stores/application-configuration-store';
|
||||
import { axiosErrorToast } from '$lib/utils/error-util';
|
||||
import { mode } from 'mode-watcher';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
let {
|
||||
userId = $bindable()
|
||||
@@ -32,7 +34,7 @@
|
||||
[m.one_month()]: 60 * 60 * 24 * 30
|
||||
};
|
||||
|
||||
async function createOneTimeAccessToken() {
|
||||
async function createLoginCode() {
|
||||
try {
|
||||
const expiration = new Date(Date.now() + availableExpirations[selectedExpiration] * 1000);
|
||||
code = await userService.createOneTimeAccessToken(expiration, userId!);
|
||||
@@ -42,6 +44,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function sendLoginCodeEmail() {
|
||||
try {
|
||||
const expiration = new Date(Date.now() + availableExpirations[selectedExpiration] * 1000);
|
||||
await userService.requestOneTimeAccessEmailAsAdmin(userId!, expiration);
|
||||
toast.success(m.login_code_email_success());
|
||||
onOpenChange(false);
|
||||
} catch (e) {
|
||||
axiosErrorToast(e);
|
||||
}
|
||||
}
|
||||
|
||||
function onOpenChange(open: boolean) {
|
||||
if (!open) {
|
||||
oneTimeLink = null;
|
||||
@@ -81,13 +94,20 @@
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
<Button
|
||||
onclick={() => createOneTimeAccessToken()}
|
||||
disabled={!selectedExpiration}
|
||||
class="mt-2 w-full"
|
||||
>
|
||||
{m.generate_code()}
|
||||
</Button>
|
||||
<Dialog.Footer class="mt-2">
|
||||
{#if $appConfigStore.emailOneTimeAccessAsAdminEnabled}
|
||||
<Button
|
||||
onclick={() => sendLoginCodeEmail()}
|
||||
variant="secondary"
|
||||
disabled={!selectedExpiration}
|
||||
>
|
||||
{m.send_email()}
|
||||
</Button>
|
||||
{/if}
|
||||
<Button onclick={() => createLoginCode()} disabled={!selectedExpiration}
|
||||
>{m.show_code()}</Button
|
||||
>
|
||||
</Dialog.Footer>
|
||||
{:else}
|
||||
<div class="flex flex-col items-center gap-2">
|
||||
<CopyToClipboard value={code!}>
|
||||
|
||||
@@ -87,10 +87,14 @@ export default class UserService extends APIService {
|
||||
return res.data as User;
|
||||
}
|
||||
|
||||
async requestOneTimeAccessEmail(email: string, redirectPath?: string) {
|
||||
async requestOneTimeAccessEmailAsUnauthenticatedUser(email: string, redirectPath?: string) {
|
||||
await this.api.post('/one-time-access-email', { email, redirectPath });
|
||||
}
|
||||
|
||||
async requestOneTimeAccessEmailAsAdmin(userId: string, expiresAt: Date) {
|
||||
await this.api.post(`/users/${userId}/one-time-access-email`, { expiresAt });
|
||||
}
|
||||
|
||||
async updateUserGroups(id: string, userGroupIds: string[]) {
|
||||
const res = await this.api.put(`/users/${id}/user-groups`, { userGroupIds });
|
||||
return res.data as User;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
export type AppConfig = {
|
||||
appName: string;
|
||||
allowOwnAccountEdit: boolean;
|
||||
emailOneTimeAccessEnabled: boolean;
|
||||
emailOneTimeAccessAsUnauthenticatedEnabled: boolean;
|
||||
emailOneTimeAccessAsAdminEnabled: boolean;
|
||||
ldapEnabled: boolean;
|
||||
disableAnimations: boolean;
|
||||
};
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
}
|
||||
];
|
||||
|
||||
if ($appConfigStore.emailOneTimeAccessEnabled) {
|
||||
if ($appConfigStore.emailOneTimeAccessAsUnauthenticatedEnabled) {
|
||||
methods.push({
|
||||
icon: LucideMail,
|
||||
title: m.email_login(),
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
id="application-configuration-email"
|
||||
icon={Mail}
|
||||
title={m.email()}
|
||||
description={m.enable_email_notifications_to_alert_users_when_a_login_is_detected_from_a_new_device_or_location()}
|
||||
description={m.configure_stmp_to_send_emails()}
|
||||
>
|
||||
<AppConfigEmailForm {appConfig} callback={updateAppConfig} />
|
||||
</CollapsibleCard>
|
||||
|
||||
@@ -39,7 +39,8 @@
|
||||
smtpFrom: z.string().email(),
|
||||
smtpTls: z.enum(['none', 'starttls', 'tls']),
|
||||
smtpSkipCertVerify: z.boolean(),
|
||||
emailOneTimeAccessEnabled: z.boolean(),
|
||||
emailOneTimeAccessAsUnauthenticatedEnabled: z.boolean(),
|
||||
emailOneTimeAccessAsAdminEnabled: z.boolean(),
|
||||
emailLoginNotificationEnabled: z.boolean()
|
||||
});
|
||||
|
||||
@@ -88,9 +89,7 @@
|
||||
await appConfigService
|
||||
.sendTestEmail()
|
||||
.then(() => toast.success(m.test_email_sent_successfully()))
|
||||
.catch(() =>
|
||||
toast.error(m.failed_to_send_test_email())
|
||||
)
|
||||
.catch(() => toast.error(m.failed_to_send_test_email()))
|
||||
.finally(() => (isSendingTestEmail = false));
|
||||
}
|
||||
</script>
|
||||
@@ -136,10 +135,16 @@
|
||||
bind:checked={$inputs.emailLoginNotificationEnabled.value}
|
||||
/>
|
||||
<CheckboxWithLabel
|
||||
id="email-login"
|
||||
label={m.email_login()}
|
||||
id="email-login-user"
|
||||
label={m.emai_login_code_requested_by_user()}
|
||||
description={m.allow_users_to_sign_in_with_a_login_code_sent_to_their_email()}
|
||||
bind:checked={$inputs.emailOneTimeAccessEnabled.value}
|
||||
bind:checked={$inputs.emailOneTimeAccessAsUnauthenticatedEnabled.value}
|
||||
/>
|
||||
<CheckboxWithLabel
|
||||
id="email-login-admin"
|
||||
label={m.email_login_code_from_admin()}
|
||||
description={m.allows_an_admin_to_send_a_login_code_to_the_user()}
|
||||
bind:checked={$inputs.emailOneTimeAccessAsAdminEnabled.value}
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
@@ -161,4 +161,4 @@
|
||||
{/snippet}
|
||||
</AdvancedTable>
|
||||
|
||||
<OneTimeLinkModal userId={userIdToCreateOneTimeLink} />
|
||||
<OneTimeLinkModal bind:userId={userIdToCreateOneTimeLink} />
|
||||
|
||||
Reference in New Issue
Block a user