Gateway setup shows a `continuum-proxy enrol` command with a one-time code; the manual settings stay behind "Set it up by hand". New members section for inviting by email, plus an /invite page to accept, and the login page honours a safe `next` redirect.
88 lines
2.4 KiB
Vue
88 lines
2.4 KiB
Vue
<script setup lang="ts">
|
|
const email = ref("");
|
|
const sent = ref(false);
|
|
const error = ref<string | null>(null);
|
|
const client = useSupabaseClient();
|
|
const passwordLogin = useRuntimeConfig().public.passwordLogin;
|
|
const password = ref("");
|
|
const route = useRoute();
|
|
|
|
// Where to go after signing in. Only a path on this site: a `next` of
|
|
// `//elsewhere.example` or `https://...` would turn the sign-in page into an
|
|
// open redirect.
|
|
const next = computed(() => {
|
|
const wanted = route.query.next;
|
|
return typeof wanted === "string" && wanted.startsWith("/") && !wanted.startsWith("//") ? wanted : "/";
|
|
});
|
|
|
|
async function signInWithPassword() {
|
|
error.value = null;
|
|
const { error: authError } = await client.auth.signInWithPassword({
|
|
email: email.value,
|
|
password: password.value,
|
|
});
|
|
if (authError) {
|
|
error.value = authError.message;
|
|
return;
|
|
}
|
|
// The Supabase module redirects away from /login once a session exists.
|
|
await navigateTo(next.value);
|
|
}
|
|
|
|
async function sendMagicLink() {
|
|
error.value = null;
|
|
const { error: authError } = await client.auth.signInWithOtp({
|
|
email: email.value,
|
|
options: next.value === "/" ? undefined : { emailRedirectTo: `${window.location.origin}${next.value}` },
|
|
});
|
|
if (authError) {
|
|
error.value = authError.message;
|
|
return;
|
|
}
|
|
sent.value = true;
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="login">
|
|
<h1>Sign in to Continuum</h1>
|
|
<form v-if="!sent" @submit.prevent="sendMagicLink">
|
|
<input v-model="email" type="email" placeholder="you@farm.com" required />
|
|
<button type="submit">Send magic link</button>
|
|
</form>
|
|
<p v-else>Check your inbox for a sign-in link.</p>
|
|
<form v-if="passwordLogin" class="login__password" @submit.prevent="signInWithPassword">
|
|
<p class="login__hint">Password sign-in is enabled for this build (testing only).</p>
|
|
<input v-model="email" type="email" placeholder="you@farm.com" required />
|
|
<input v-model="password" type="password" placeholder="password" required autocomplete="current-password" />
|
|
<button type="submit">Sign in</button>
|
|
</form>
|
|
<p v-if="error" class="login__error">{{ error }}</p>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.login {
|
|
max-width: 320px;
|
|
margin: 4rem auto;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.75rem;
|
|
}
|
|
|
|
.login__password {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.75rem;
|
|
}
|
|
|
|
.login__hint {
|
|
font-size: 0.75rem;
|
|
opacity: 0.7;
|
|
}
|
|
|
|
.login__error {
|
|
color: #f2604c;
|
|
}
|
|
</style>
|