Skip to content

Instantly share code, notes, and snippets.

@arnu515
Last active April 30, 2021 04:23
Show Gist options
  • Save arnu515/56c8e05f0dfcc858025d7b927f5d5b82 to your computer and use it in GitHub Desktop.
Save arnu515/56c8e05f0dfcc858025d7b927f5d5b82 to your computer and use it in GitHub Desktop.
Dev.to Implement Passwordless sign-in to your apps
<script lang="ts">
import Auth from "./lib/components/Auth.svelte";
import Code from "./lib/components/Code.svelte";
let sentLink = false;
let token = localStorage.getItem("token");
async function getUser() {
if (!token) return;
try {
const res = await fetch("http://localhost:5000/api/auth/user", {
headers: { Authorization: "Bearer " + token }
});
const data = await res.json();
console.log(data);
if (res.ok && data.ok && data.user) {
return data.user;
}
localStorage.removeItem("token");
return null;
} catch (e) {
console.error(e);
localStorage.removeItem("token");
alert("An unknown error occured");
return null;
}
}
</script>
<h1 class="w3-center">Welcome</h1>
{#if !token}
<div class="w3-container">
{#if !sentLink}
<Auth on:prompt-code="{() => (sentLink = true)}" />
{:else}
<Code
on:authenticated="{({ detail: token }) => {
localStorage.setItem('token', token);
window.location.reload();
}}"
/>
{/if}
</div>
{:else}
{#await getUser()}
<p class="w3-center">Fetching user information</p>
{:then user}
{#if user}
<p class="w3-center">Hello, {user.username}</p>
<p class="w3-center">
<button
class="w3-button w3-black w3-hover-black"
on:click="{() => {
localStorage.removeItem('token');
window.location.reload();
}}">Logout</button
>
</p>
{:else}
<p class="w3-center">An error occured while fetching user data</p>
{/if}
{/await}
{/if}
<script lang="ts">
import { createEventDispatcher } from "svelte";
const d = createEventDispatcher();
async function requestCode() {
const email = (document.getElementById("email") as HTMLInputElement)?.value;
if (!email?.trim()) return;
try {
const res = await fetch("http://localhost:5000/api/auth/send_magic_link", {
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({ email }),
method: "POST"
});
const data = await res.json();
if (res.ok && data.ok) d("prompt-code");
else {
console.error(data);
alert(data.error || res.statusText);
}
} catch (e) {
console.error(e);
alert("An unknown error occured");
}
}
</script>
<div class="w3-border w3-border-gray w3-padding w3-rounded">
<h2 class="w3-center">Authenticate</h2>
<form class="w3-margin" on:submit|preventDefault="{requestCode}">
<p>
<label for="email">Email</label>
<input type="email" id="email" class="w3-input w3-border w3-border-gray" />
</p>
<p>
<button class="w3-button w3-black w3-hover-black" style="width: 100%"
>Get magic link</button
>
</p>
</form>
</div>
import { Router } from "express";
import User from "../models/User";
import Code from "../models/Code";
import nodemailer from "nodemailer";
import { sign, verify } from "jsonwebtoken";
const router = Router();
router.post("/send_magic_link", async (req, res) => {
const { email } = req.body;
if (typeof email !== "string" || !email.trim())
return res.status(400).json({
error: "Invalid email",
error_description: "Please provide a valid email",
});
const userId = (await User.findOne({ email }))?.id;
const code = Math.floor(Math.random() * 899999 + 100000);
// Expire after 15 minutes
const c = new Code({
code,
userId,
email,
expiresAt: Date.now() + 15 * 60 * 1000,
});
await c.save();
const transport = nodemailer.createTransport({
host: "smtp.mailtrap.io",
port: 2525,
auth: {
user: "d9c780094b04e2",
pass: "875f89b26f98c2",
},
});
transport.verify((e) => {
if (e) console.error(e);
});
const message = {
from: "test@example.com",
to: email,
text: `Enter this code: ${code}`,
html: `<p>Enter this code: <b>${code}</b></p>`,
};
transport.sendMail(message, (err) => {
if (err) console.error("An error occured while sending email", err);
else console.log("Mail sent");
});
return res.status(200).json({ ok: true });
});
router.get("/token", async (req, res) => {
const { code: codeFromQs } = req.query;
if (typeof codeFromQs !== "string" || isNaN(parseInt(codeFromQs)))
return res.status(400).json({
error: "Invalid code",
error_description: "Please send a valid code in the querystring",
});
const code = parseInt(codeFromQs);
const c = await Code.findOne({ code }).exec();
if (!c)
return res.status(400).json({
error: "Invalid code",
error_description: "Please send a valid code in the querystring",
});
const { email, userId } = c as any;
let user = null;
if (userId) {
user = await User.findById(userId).exec();
if (!user)
return res.status(400).json({
error: "Invalid code",
error_description: "Please send a valid code in the querystring",
});
} else {
user = new User({ email, username: email.split("@")[0] });
await user.save();
}
// Exp in 1 week
const token = sign(
{ id: user._id.toString() },
process.env.SECRET || "secret",
{
expiresIn: 604800,
}
);
return res.status(200).json({ ok: true, token, user });
});
router.get("/user", async (req, res) => {
const authHeader = req.headers.authorization;
if (
!authHeader ||
typeof authHeader !== "string" ||
authHeader.split(" ")?.length !== 2 ||
authHeader.split(" ")[0].toLowerCase() !== "bearer"
)
return res.status(401).json({ error: "Invalid auth header" });
const identity = verify(
authHeader.split(" ")[1],
process.env.SECRET || "secret"
) as any;
if (typeof identity === "string")
return res.status(401).json({ error: "Invalid token" });
if (typeof identity.id !== "string")
return res.status(401).json({ error: "Invalid token" });
const user = await User.findById(identity.id);
if (!user) return res.status(401).json({ error: "Invalid token" });
return res.status(200).json({ ok: true, user });
});
export default router;
<script lang="ts">
import { createEventDispatcher } from "svelte";
const d = createEventDispatcher();
async function getToken() {
const code = (document.getElementById("code") as HTMLInputElement)?.value;
if (!code) return;
try {
const res = await fetch(
"http://localhost:5000/api/auth/token?code=" + code
);
const data = await res.json();
if (res.ok && data.ok) d("authenticated", data.token);
else {
console.error(data);
alert(data.error || res.statusText);
}
} catch (e) {
console.error(e);
alert("An unknown error occured");
}
}
</script>
<div class="w3-border w3-border-gray w3-padding w3-rounded">
<h2 class="w3-center">Enter code</h2>
<form class="w3-margin" on:submit|preventDefault="{getToken}">
<p>
<label for="code">Enter the code sent to you by email</label>
<input type="number" id="code" class="w3-input w3-border w3-border-gray" />
</p>
<p>
<button class="w3-button w3-black w3-hover-black" style="width: 100%"
>Authenticate</button
>
</p>
</form>
</div>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment