Skip to content

Commit

Permalink
Add fastify cookie based auth
Browse files Browse the repository at this point in the history
  • Loading branch information
felixmosh committed Sep 9, 2021
1 parent f7d2b43 commit 4794c56
Show file tree
Hide file tree
Showing 7 changed files with 1,189 additions and 19 deletions.
11 changes: 8 additions & 3 deletions examples/with-fastify-auth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,13 @@ This example shows how to use [Fastify.js](https://www.fastify.io/) as a server
the `session` cookie / basic auth header automatically to **each** request.


### Usage
1. Navigate to `/login`
### Usage with Basic Auth
1. Navigate to `/basic/login`
2. Fill in username: `bull` & password: `board`

*Based on: https://github.com/fastify/fastify-basic-auth*

### Usage with Cookie Auth
1. Navigate to `/cooki/login`
2. Fill in username: `bull` & password: `board`

Based on: https://github.com/fastify/fastify-basic-auth
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ const { FastifyAdapter } = require('@bull-board/fastify');
const { createBullBoard } = require('@bull-board/api');
const { BullMQAdapter } = require('@bull-board/api/bullMQAdapter');

module.exports.bullBoardWithAuth = function bullBoardWithAuth(fastify, { queue }, next) {
const authenticate = { realm: 'Westeros' };
module.exports.basicAuth = function basicAuth(fastify, { queue }, next) {
const authenticate = { realm: 'Bull-Board' };
function validate(username, password, req, reply, done) {
if (username === 'bull' && password === 'board') {
done();
Expand All @@ -22,13 +22,13 @@ module.exports.bullBoardWithAuth = function bullBoardWithAuth(fastify, { queue }
serverAdapter,
});

serverAdapter.setBasePath('/ui');
fastify.register(serverAdapter.registerPlugin(), { prefix: '/ui' });
serverAdapter.setBasePath('/basic/ui');
fastify.register(serverAdapter.registerPlugin(), { prefix: '/basic/ui' });
fastify.route({
method: 'GET',
url: '/login',
url: '/basic/login',
handler: async (req, reply) => {
reply.redirect('/ui');
reply.redirect('/basic/ui');
},
});
fastify.addHook('onRequest', fastify.basicAuth);
Expand Down
91 changes: 91 additions & 0 deletions examples/with-fastify-auth/cookieAuth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
const { FastifyAdapter } = require('@bull-board/fastify');
const { createBullBoard } = require('@bull-board/api');
const { BullMQAdapter } = require('@bull-board/api/bullMQAdapter');
const pointOfView = require('point-of-view');
const path = require('path');

module.exports.cookieAuth = function cookieAuth(fastify, { queue }, next) {
fastify.register(require('fastify-cookie'), {
secret: 'my-secret', // for cookies signature
parseOptions: {}, // options for parsing cookies
});

fastify.register(require('fastify-jwt'), {
secret: 'supersecret',
cookie: {
cookieName: 'token',
},
});

function validate(req, reply, done) {
if (username === 'bull' && password === 'board') {
done();
} else {
done(new Error('Unauthorized'));
}
}

fastify.decorate('validate', validate);

fastify.after(() => {
const serverAdapter = new FastifyAdapter();

createBullBoard({
queues: [new BullMQAdapter(queue)],
serverAdapter,
});

serverAdapter.setBasePath('/cookie/ui');
fastify.register(serverAdapter.registerPlugin(), { prefix: '/cookie/ui' });

fastify.register(pointOfView, {
engine: {
ejs: require('ejs'),
},
root: path.resolve('./views'),
});

fastify.route({
method: 'GET',
url: '/cookie/login',
handler: async (req, reply) => {
reply.view('login.ejs');
},
});

fastify.route({
method: 'POST',
url: '/cookie/login',
handler: async (req, reply) => {
const { username = '', password = '' } = req.body;

if (username === 'bull' && password === 'board') {
const token = await reply.jwtSign({
name: 'foo',
role: ['admin', 'spy'],
});

reply
.setCookie('token', token, {
path: '/cookie',
secure: false, // send cookie over HTTPS only
httpOnly: true,
sameSite: true, // alternative CSRF protection
})
.send({ success: true, url: '/cookie/ui' });
} else {
reply.code(401).send({ error: 'invalid_username_password' });
}
},
});
fastify.addHook('onRequest', (request, reply, next) => {
if (request.url === '/cookie/login') {
return next();
}

return request.jwtVerify();
});
});

next();
};
6 changes: 4 additions & 2 deletions examples/with-fastify-auth/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const { bullBoardWithAuth } = require('./bull-board-plugin');
const { Queue: QueueMQ, Worker, QueueScheduler } = require('bullmq');
const fastify = require('fastify');
const { basicAuth } = require('./basicAuth');
const { cookieAuth } = require('./cookieAuth');

const sleep = (t) => new Promise((resolve) => setTimeout(resolve, t * 1000));

Expand Down Expand Up @@ -39,7 +40,8 @@ const run = async () => {

const app = fastify();

app.register(bullBoardWithAuth, { queue: exampleBullMq });
app.register(basicAuth, { queue: exampleBullMq });
app.register(cookieAuth, { queue: exampleBullMq });

app.get('/add', (req, reply) => {
const opts = req.query.opts || {};
Expand Down
9 changes: 7 additions & 2 deletions examples/with-fastify-auth/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,19 @@
"description": "Example of how to use Fastify server with bull-board",
"main": "index.js",
"scripts": {
"start": "node index.js",
"start": "nodemon index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "felixmosh",
"license": "ISC",
"dependencies": {
"@bull-board/fastify": "^3.5.4",
"bullmq": "^1.46.3",
"fastify-basic-auth": "^2.1.0"
"fastify-basic-auth": "^2.1.0",
"fastify-cookie": "^5.3.1",
"fastify-jwt": "^3.0.1"
},
"devDependencies": {
"nodemon": "^2.0.12"
}
}
177 changes: 177 additions & 0 deletions examples/with-fastify-auth/views/login.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link href="https://fonts.googleapis.com/css2?family=Ubuntu:wght@300;400;500&display=swap"
rel="stylesheet" />
<style>
body {
background: #f5f8fa;
font-family: 'Ubuntu', sans-serif;
font-weight: 400;
line-height: 1.25em;
margin: 0;
font-size: 16px;
color: #454b52;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.login-page {
width: 360px;
padding: 8% 0 0;
margin: auto;
}
.form {
position: relative;
z-index: 1;
background: #FFFFFF;
max-width: 360px;
margin: 0 auto 100px;
padding: 45px;
text-align: center;
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.2), 0 5px 5px 0 rgba(0, 0, 0, 0.24);
}
.form input {
font-family: inherit;
outline: 0;
background: #f2f2f2;
width: 100%;
border: 0;
margin: 0 0 15px;
padding: 15px;
box-sizing: border-box;
font-size: 14px;
}
.form button {
font-family: inherit;
text-transform: uppercase;
outline: 0;
background: hsl(217, 22%, 24%);
width: 100%;
border: 0;
padding: 15px;
color: #FFFFFF;
font-size: 14px;
-webkit-transition: all 0.3 ease;
transition: all 0.3 ease;
cursor: pointer;
}
.form button:hover, .form button:active, .form button:focus {
background: hsl(217, 22%, 28%);
}
.form .message {
margin: 15px 0 0;
color: #b3b3b3;
font-size: 12px;
}
.form .message a {
color: hsl(217, 22%, 24%);
text-decoration: none;
}
.container {
position: relative;
z-index: 1;
max-width: 300px;
margin: 0 auto;
}
.container:before, .container:after {
content: "";
display: block;
clear: both;
}
.container .info {
margin: 50px auto;
text-align: center;
}
.container .info h1 {
margin: 0 0 15px;
padding: 0;
font-size: 36px;
font-weight: 300;
color: #1a1a1a;
}
.container .info span {
color: #4d4d4d;
font-size: 12px;
}
.container .info span a {
color: #000000;
text-decoration: none;
}
.container .info span .fa {
color: #EF3B3A;
}
#error-message {
display: none;
color: #EF3B3A;
margin-bottom: 0.5rem;
}
</style>
<title>Bull-Board</title>
</head>
<body>
<div class="login-page">
<div class="form">
<div id="error-message">Invalid username or password.</div>
<form class="login-form" id="loginForm" method="post" action="/cookie/login"
onsubmit="return handleSubmit(this)">
<input type="text" name="username" placeholder="Username" />
<input type="password" name="password" placeholder="Password" />
<button>Login</button>

<p class="message">Username: bull, Password: board</p>
</form>
</div>
</div>

<script>
function handleSubmit(form) {
const url = form.action;
const method = form.method;
const username = form.username.value;
const password = form.password.value;
const errorMessage = document.getElementById('error-message')
if (!username || !password) {
return false;
}
errorMessage.style.display = 'none';
fetch(url, {
method: method.toUpperCase(),
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ username, password })
}).then((res) =>
res.status !== 200 ? Promise.reject(res.json()) : res.json()
)
.then(res => res.error ? Promise.reject(res.error) : res)
.then(({ url }) => {
window.location = url
})
.catch(() => {
errorMessage.style.display = 'block'
})
return false
}
</script>
</body>
</html>
Loading

0 comments on commit 4794c56

Please sign in to comment.