mirror of
https://github.com/BradNut/node-auth
synced 2025-09-08 17:40:17 +00:00
Register, send email, password reset start.
This commit is contained in:
parent
98298d07d2
commit
3bc880559f
21 changed files with 1480 additions and 194 deletions
1
package-lock.json → api/package-lock.json
generated
1
package-lock.json → api/package-lock.json
generated
|
|
@ -5,6 +5,7 @@
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
|
"name": "node-auth",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
50
api/src/accounts/verify.js
Normal file
50
api/src/accounts/verify.js
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
import crypto from 'crypto'
|
||||||
|
const { ROOT_DOMAIN, JWT_SIGNATURE } = process.env
|
||||||
|
|
||||||
|
export async function createVerifyEmailToken(email) {
|
||||||
|
try {
|
||||||
|
// Auth String, JWT Signature, email
|
||||||
|
const authString = `${JWT_SIGNATURE}:${email}`
|
||||||
|
return crypto.createHash('sha256').update(authString).digest('hex');
|
||||||
|
} catch (e) {
|
||||||
|
console.log('e', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createVerifyEmailLink(email) {
|
||||||
|
try {
|
||||||
|
// Create token
|
||||||
|
const emailToken = await createVerifyEmailToken(email);
|
||||||
|
// Encode url string
|
||||||
|
const URIencodedEmail = encodeURIComponent(email);
|
||||||
|
// Return link for verification
|
||||||
|
return `https://${ROOT_DOMAIN}/verify/${URIencodedEmail}/${emailToken}`
|
||||||
|
} catch (e) {
|
||||||
|
console.log('e', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function validateVerifyEmail(token, email) {
|
||||||
|
try {
|
||||||
|
// Create a hash aka token
|
||||||
|
const emailToken = await createVerifyEmailToken(email);
|
||||||
|
// Compare hash with token
|
||||||
|
const isValid = emailToken === token
|
||||||
|
// If successful
|
||||||
|
if (isValid) {
|
||||||
|
// update user, to make them verified
|
||||||
|
const { user } = await import ("../user/user.js")
|
||||||
|
await user.updateOne({
|
||||||
|
'email.address': email,
|
||||||
|
}, {
|
||||||
|
$set: { 'email.verified': true },
|
||||||
|
})
|
||||||
|
// Return success
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
} catch (e) {
|
||||||
|
console.log('e', e);
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,6 +12,7 @@ import { logUserIn } from './accounts/logUserIn.js'
|
||||||
import { logUserOut } from './accounts/logUserOut.js'
|
import { logUserOut } from './accounts/logUserOut.js'
|
||||||
import { getUserFromCookies } from './accounts/user.js'
|
import { getUserFromCookies } from './accounts/user.js'
|
||||||
import { sendEmail, mailInit } from './mail/index.js'
|
import { sendEmail, mailInit } from './mail/index.js'
|
||||||
|
import { createVerifyEmailLink, validateVerifyEmail } from './accounts/verify.js'
|
||||||
|
|
||||||
// ESM specific "features"
|
// ESM specific "features"
|
||||||
const __filename = fileURLToPath(import.meta.url)
|
const __filename = fileURLToPath(import.meta.url)
|
||||||
|
|
@ -22,10 +23,6 @@ const app = fastify()
|
||||||
async function startApp() {
|
async function startApp() {
|
||||||
try {
|
try {
|
||||||
await mailInit()
|
await mailInit()
|
||||||
await sendEmail({
|
|
||||||
subject: "New func",
|
|
||||||
html: /*html*/ `<h2>New HTML who is?</h2>`,
|
|
||||||
})
|
|
||||||
|
|
||||||
app.register(fastifyCors, {
|
app.register(fastifyCors, {
|
||||||
origin: [/\.nodeauth.dev/, 'https://nodeauth.dev'],
|
origin: [/\.nodeauth.dev/, 'https://nodeauth.dev'],
|
||||||
|
|
@ -46,7 +43,14 @@ async function startApp() {
|
||||||
request.body.email,
|
request.body.email,
|
||||||
request.body.password
|
request.body.password
|
||||||
)
|
)
|
||||||
|
// If account creation was successful
|
||||||
if (userId) {
|
if (userId) {
|
||||||
|
const emailLink = await createVerifyEmailLink(request.body.email)
|
||||||
|
await sendEmail({
|
||||||
|
to: request.body.email,
|
||||||
|
subject: "Verify your email",
|
||||||
|
html: /*html*/ `<a href="${emailLink}">verify</a>`,
|
||||||
|
})
|
||||||
await logUserIn(userId, request, reply)
|
await logUserIn(userId, request, reply)
|
||||||
reply.send({
|
reply.send({
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -85,6 +89,22 @@ async function startApp() {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
app.post('/api/verify', {}, async (request, reply) => {
|
||||||
|
try {
|
||||||
|
const { token, email } = request.body
|
||||||
|
console.log('token, email', token, email);
|
||||||
|
const isValid = await validateVerifyEmail(token, email)
|
||||||
|
console.log(`Is Valid: ${isValid}`)
|
||||||
|
if (isValid) {
|
||||||
|
return reply.code(200).send()
|
||||||
|
}
|
||||||
|
return reply.code(401).send()
|
||||||
|
} catch (e) {
|
||||||
|
console.error('e', e);
|
||||||
|
return reply.code(401).send()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
app.post('/api/authorize', {}, async (request, reply) => {
|
app.post('/api/authorize', {}, async (request, reply) => {
|
||||||
try {
|
try {
|
||||||
console.log(request.body.email, request.body.password)
|
console.log(request.body.email, request.body.password)
|
||||||
|
|
@ -4,6 +4,7 @@ let mail
|
||||||
|
|
||||||
export async function mailInit() {
|
export async function mailInit() {
|
||||||
let testAccount = await nodemailer.createTestAccount();
|
let testAccount = await nodemailer.createTestAccount();
|
||||||
|
console.log(`Test Account: ${JSON.stringify(testAccount)}`)
|
||||||
|
|
||||||
mail = nodemailer.createTransport({
|
mail = nodemailer.createTransport({
|
||||||
host: "smtp.ethereal.email",
|
host: "smtp.ethereal.email",
|
||||||
|
|
@ -1,94 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Document</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Hello</h1>
|
|
||||||
<h3>Register Form</h3>
|
|
||||||
<form id="register-form">
|
|
||||||
<input type="email" name="email">
|
|
||||||
<input type="password" name="password">
|
|
||||||
<button type="submit">Register</button>
|
|
||||||
</form>
|
|
||||||
<br/>
|
|
||||||
<hr />
|
|
||||||
<br/>
|
|
||||||
<h3>Login Form</h3>
|
|
||||||
<form id="login-form">
|
|
||||||
<input type="email" name="email">
|
|
||||||
<input type="password" name="password">
|
|
||||||
<button type="submit">Login</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
<hr />
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<button onclick="logout()">Logout</button>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
async function logout() {
|
|
||||||
try {
|
|
||||||
const res = await fetch('/api/logout', {
|
|
||||||
method: "POST",
|
|
||||||
})
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
;(() => {
|
|
||||||
const registerForm = document.getElementById("register-form")
|
|
||||||
|
|
||||||
registerForm.addEventListener("submit", async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
try {
|
|
||||||
const values = Object.values(registerForm).reduce((obj, field) => {
|
|
||||||
if (field.name) {
|
|
||||||
obj[field.name] = field.value
|
|
||||||
}
|
|
||||||
return obj
|
|
||||||
}, {})
|
|
||||||
const res = await fetch('/api/register', {
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify(values),
|
|
||||||
headers: { "Content-type": "application/json; charset=UTF-8"},
|
|
||||||
})
|
|
||||||
console.log("values", values)
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Find form element
|
|
||||||
const loginForm = document.getElementById("login-form")
|
|
||||||
// Wait for event
|
|
||||||
loginForm.addEventListener("submit", async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
try {
|
|
||||||
// Get form values
|
|
||||||
const values = Object.values(loginForm).reduce((obj, field) => {
|
|
||||||
if (field.name) {
|
|
||||||
obj[field.name] = field.value
|
|
||||||
}
|
|
||||||
return obj
|
|
||||||
}, {})
|
|
||||||
// Submit
|
|
||||||
const res = await fetch('/api/authorize', {
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify(values),
|
|
||||||
headers: { "Content-type": "application/json; charset=UTF-8"},
|
|
||||||
})
|
|
||||||
console.log("values", values)
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})()
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,96 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>Document</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Hello</h1>
|
|
||||||
<h3>Register Form</h3>
|
|
||||||
<form id="register-form">
|
|
||||||
<input type="email" name="email" />
|
|
||||||
<input type="password" name="password" />
|
|
||||||
<button type="submit">Register</button>
|
|
||||||
</form>
|
|
||||||
<br />
|
|
||||||
<hr />
|
|
||||||
<br />
|
|
||||||
<h3>Login Form</h3>
|
|
||||||
<form id="login-form">
|
|
||||||
<input type="email" name="email" />
|
|
||||||
<input type="password" name="password" />
|
|
||||||
<button type="submit">Login</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<br />
|
|
||||||
<hr />
|
|
||||||
<br />
|
|
||||||
|
|
||||||
<button onclick="logout()">Logout</button>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
async function logout() {
|
|
||||||
try {
|
|
||||||
const res = await fetch('https://api.nodeauth.dev/api/logout', {
|
|
||||||
method: 'POST',
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(() => {
|
|
||||||
const registerForm = document.getElementById('register-form');
|
|
||||||
|
|
||||||
registerForm.addEventListener('submit', async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
try {
|
|
||||||
const values = Object.values(registerForm).reduce((obj, field) => {
|
|
||||||
if (field.name) {
|
|
||||||
obj[field.name] = field.value;
|
|
||||||
}
|
|
||||||
return obj;
|
|
||||||
}, {});
|
|
||||||
const res = await fetch('https://api.nodeauth.dev/api/register', {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify(values),
|
|
||||||
credentials: 'include',
|
|
||||||
headers: { 'Content-type': 'application/json; charset=UTF-8' },
|
|
||||||
});
|
|
||||||
console.log('values', values);
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Find form element
|
|
||||||
const loginForm = document.getElementById('login-form');
|
|
||||||
// Wait for event
|
|
||||||
loginForm.addEventListener('submit', async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
try {
|
|
||||||
// Get form values
|
|
||||||
const values = Object.values(loginForm).reduce((obj, field) => {
|
|
||||||
if (field.name) {
|
|
||||||
obj[field.name] = field.value;
|
|
||||||
}
|
|
||||||
return obj;
|
|
||||||
}, {});
|
|
||||||
// Submit
|
|
||||||
const res = await fetch('https://api.nodeauth.dev/api/authorize', {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify(values),
|
|
||||||
credentials: 'include',
|
|
||||||
headers: { 'Content-type': 'application/json; charset=UTF-8' },
|
|
||||||
});
|
|
||||||
console.log('values', values);
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
1322
ui/package-lock.json
generated
Normal file
1322
ui/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
17
ui/package.json
Normal file
17
ui/package.json
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"name": "ui",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"type": "module",
|
||||||
|
"main": "./src/index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"cross-fetch": "^3.1.4",
|
||||||
|
"fastify": "^3.15.0",
|
||||||
|
"fastify-static": "^4.0.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
65
ui/src/index.js
Normal file
65
ui/src/index.js
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
import https from 'https'
|
||||||
|
import { fastify } from 'fastify'
|
||||||
|
import fastifyStatic from 'fastify-static'
|
||||||
|
import fetch from 'cross-fetch'
|
||||||
|
import path from 'path'
|
||||||
|
import { fileURLToPath } from 'url'
|
||||||
|
|
||||||
|
// ESM specific "features"
|
||||||
|
const __filename = fileURLToPath(import.meta.url)
|
||||||
|
const __dirname = path.dirname(__filename)
|
||||||
|
|
||||||
|
const app = fastify()
|
||||||
|
|
||||||
|
async function startApp() {
|
||||||
|
try {
|
||||||
|
app.register(fastifyStatic, {
|
||||||
|
root: path.join(__dirname, "public"),
|
||||||
|
})
|
||||||
|
|
||||||
|
app.get('/verify/:email/:token', {}, async ( request, reply ) => {
|
||||||
|
try {
|
||||||
|
const { email, token } = request.params
|
||||||
|
console.log('request', request.params.email, request.params.token);
|
||||||
|
const values = {
|
||||||
|
email,
|
||||||
|
token,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fixes UNABLE_TO_GET_ISSUER_CERT_LOCALLY, ok to do so because we do on a route by route basis and it is on our servers
|
||||||
|
const httpsAgent = new https.Agent({
|
||||||
|
rejectUnauthorized: false,
|
||||||
|
})
|
||||||
|
const res = await fetch('https://api.nodeauth.dev/api/verify', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(values),
|
||||||
|
credentials: 'include',
|
||||||
|
agent: httpsAgent,
|
||||||
|
headers: { 'Content-type': 'application/json; charset=UTF-8' },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.status === 200) {
|
||||||
|
return reply.redirect('/')
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('res', res.status);
|
||||||
|
reply.code(401).send()
|
||||||
|
} catch (e) {
|
||||||
|
console.log('e', e);
|
||||||
|
reply.send({
|
||||||
|
data: {
|
||||||
|
status: "FAILED",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const PORT = 5000;
|
||||||
|
await app.listen(PORT);
|
||||||
|
console.log(`🚀 Server Listening at port: ${PORT}`);
|
||||||
|
} catch (e) {
|
||||||
|
console.log('e', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
startApp();
|
||||||
Loading…
Reference in a new issue