Skip to content

Commit

Permalink
Encapuslated auth components.
Browse files Browse the repository at this point in the history
  • Loading branch information
amyjko committed Oct 27, 2023
1 parent b204cef commit abb2847
Show file tree
Hide file tree
Showing 9 changed files with 358 additions and 316 deletions.
4 changes: 2 additions & 2 deletions src/db/CreatorDatabase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ import type { User } from 'firebase/auth';
export const CreatorCollection = 'creators';

/** The type for a record returned by our cloud functions */
export type CreatorSchema = {
type CreatorSchema = {
uid: string;
name: string | null;
email: string | null;
};

export class Creator {
/** This is the domain we append to work around the lack of Firebase support for raw usernames. */
static CreatorUsernameEmailDomain = '@wordplay.dev';
static CreatorUsernameEmailDomain = '@u.wordplay.dev';
readonly data: CreatorSchema;
constructor(data: CreatorSchema) {
this.data = data;
Expand Down
6 changes: 3 additions & 3 deletions src/locale/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -4198,14 +4198,14 @@
"play": "You're logged in, we can save your projects online now! Want to create something?",
"emailrules": [
"• Don't provide your email if you are *12 or younger*.",
"• If you *lose access* to your email account, you won't be able to update your email address."
"• If you *lose access* to your email account, you won't be able to log in or update your email address."
],
"usernamerules": [
"• *Usernames* should not contain identiable information (e.g., names), cannot be an email addresses, and should be at least 5 characters long",
"• *Passwords* must be at least 10 characters long; if you're not using a password manager, choose three long words you'll remember.",
"• *If you forget your password*, you can't recover your account, since we have no other way to know it's you. Store your password somewhere safe, like a password manager"
],
"passwordreminder": "It looks like you're creating an account. Reveal it above, and make sure you wrote it down correctly, then submit again to create your account.",
"passwordreminder": "It looks like you're creating an account. Check your password, make sure you stored it safely and correctly, then submit again to create your account.",
"change": "Want to change your email? Submit a new one and we'll send a confirmation to the old one.",
"sent": "Check your email for a login link. Patience, email can be slow!",
"logout": "Leaving a shared device and want to keep your projects private? Logout and we'll remove your projects from this device. They will still be stored online.",
Expand All @@ -4220,7 +4220,7 @@
"invalid": "This link isn't valid.",
"email": "This email wasn't valid.",
"failure": "Unable to login :(",
"offline": "You appear to be offline.",
"offline": "We couldn't reach the cloud ☁️.",
"unchanged": "We couldn't change your email address, but we don't know why.",
"delete": "We couldn't delete your account, but we don't know why.",
"wrongPassword": "Not a valid username and password. Either your password is wrong, or someone else has this username."
Expand Down
130 changes: 130 additions & 0 deletions src/routes/login/EmailLogin.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<script lang="ts">
import {
isSignInWithEmailLink,
sendSignInLinkToEmail,
signInWithEmailLink,
} from 'firebase/auth';
import Subheader from '@components/app/Subheader.svelte';
import TextField from '@components/widgets/TextField.svelte';
import { locales } from '@db/Database';
import MarkupHtmlView from '@components/concepts/MarkupHTMLView.svelte';
import Button from '@components/widgets/Button.svelte';
import validEmail from '@db/validEmail';
import LoginForm from './LoginForm.svelte';
import { analytics, auth } from '@db/firebase';
import { onMount } from 'svelte';
import getLoginErrorDescription from './getAuthErrorDescription';
import { getUser } from '@components/project/Contexts';
import { logEvent } from 'firebase/analytics';
import { goto } from '$app/navigation';
/** The current email text in the text field */
let email: string;
/** Whether the form was submitted */
let submitted = false;
/** Feedback about the submission */
let feedback: string | undefined = undefined;
/** Get the current login context */
let user = getUser();
/** Whether the form is ready to be submitted */
$: submittable = !submitted && validEmail(email);
/** When the page is mounted, see if the link is an email sign in link, and if so, attempt to finish logging in. */
onMount(() => {
if (auth && isSignInWithEmailLink(auth, window.location.href))
finishEmailLogin();
});
async function sendLoginEmail() {
if (auth && submittable) {
try {
/** If this is already a link, finish the login with the email they entered. */
if (isSignInWithEmailLink(auth, window.location.href))
finishEmailLogin();
else {
// Ask Firebase to send an email.
await sendSignInLinkToEmail(auth, email, {
url: `${location.origin}/login`,
handleCodeInApp: true,
});
// Remember the email in local storage so we don't have to ask for it again
// after returning to the link above.
window.localStorage.setItem('email', email);
submitted = true;
feedback = $locales.get((l) => l.ui.page.login.prompt.sent);
}
} catch (err) {
feedback = getLoginErrorDescription($locales, err);
}
}
}
function finishEmailLogin(): string | undefined {
if (auth) {
try {
// If this is on the same device and browser, then the email should be in local storage.
const storedEmail = window.localStorage.getItem('email');
// If there's no email, prompt for one.
if (storedEmail === null && email === '') {
feedback = $locales.get(
(l) => l.ui.page.login.prompt.enter
);
}
// If no user, create an account with the email.
else if ($user === null) {
signInWithEmailLink(
auth,
storedEmail ?? email,
window.location.href
).then(() => {
// Remove the email we might have stored.
window.localStorage.removeItem('email');
// Provide success feedback (which likely won't be visible, since we're navigating immediately)
feedback = $locales.get(
(l) => l.ui.page.login.prompt.success
);
// Log login event in analytics
if (analytics) logEvent(analytics, 'login');
// Remove the query on the URL, showing the profile view.
goto('/login');
});
}
} catch (err) {
feedback = getLoginErrorDescription($locales, err);
}
}
return undefined;
}
</script>

<Subheader>{$locales.get((l) => l.ui.page.login.subheader.email)}</Subheader>
<LoginForm submit={sendLoginEmail} {feedback}>
<div>
<TextField
kind="email"
description={$locales.get(
(l) => l.ui.page.login.field.email.description
)}
placeholder={$locales.get(
(l) => l.ui.page.login.field.email.placeholder
)}
bind:text={email}
editable={!submitted}
/>
<Button
submit
background
tip={$locales.get((l) => l.ui.page.login.button.login)}
active={submittable}
action={() => undefined}>&gt;</Button
>
</div>
<MarkupHtmlView
markup={$locales.get((l) => l.ui.page.login.prompt.emailrules)}
/>
</LoginForm>
Loading

0 comments on commit abb2847

Please sign in to comment.