Last active
April 30, 2021 04:23
-
-
Save arnu515/56c8e05f0dfcc858025d7b927f5d5b82 to your computer and use it in GitHub Desktop.
Dev.to Implement Passwordless sign-in to your apps
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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: "[email protected]", | |
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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