mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-28 18:26:54 +00:00
feat(api): sessions management (#49499)
Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
This commit is contained in:
+29
-5
@@ -1,12 +1,24 @@
|
||||
import fastifyAuth0 from 'fastify-auth0-verify';
|
||||
import Fastify from 'fastify';
|
||||
import middie from '@fastify/middie';
|
||||
import fastifySession from '@fastify/session';
|
||||
import fastifyCookie from '@fastify/cookie';
|
||||
import MongoStore from 'connect-mongo';
|
||||
|
||||
import jwtAuthz from './plugins/fastify-jwt-authz';
|
||||
import sessionAuth from './plugins/session-auth';
|
||||
import { testRoutes } from './routes/test';
|
||||
import { auth0Routes } from './routes/auth0';
|
||||
import { dbConnector } from './db';
|
||||
import { auth0Verify, testMiddleware } from './middleware';
|
||||
import { AUTH0_AUDIENCE, AUTH0_DOMAIN, NODE_ENV, PORT } from './utils/env';
|
||||
import { testMiddleware } from './middleware';
|
||||
import {
|
||||
AUTH0_AUDIENCE,
|
||||
AUTH0_DOMAIN,
|
||||
NODE_ENV,
|
||||
PORT,
|
||||
MONGOHQ_URL,
|
||||
SESSION_SECRET
|
||||
} from './utils/env';
|
||||
|
||||
const fastify = Fastify({
|
||||
logger: { level: NODE_ENV === 'development' ? 'debug' : 'fatal' }
|
||||
@@ -19,6 +31,19 @@ fastify.get('/', async (_request, _reply) => {
|
||||
const start = async () => {
|
||||
// NOTE: Awaited to ensure `.use` is registered on `fastify`
|
||||
await fastify.register(middie);
|
||||
await fastify.register(fastifyCookie);
|
||||
await fastify.register(fastifySession, {
|
||||
secret: SESSION_SECRET,
|
||||
rolling: false,
|
||||
saveUninitialized: false,
|
||||
cookie: {
|
||||
maxAge: 1000 * 60 * 60, // 1 hour
|
||||
secure: NODE_ENV !== 'development'
|
||||
},
|
||||
store: MongoStore.create({
|
||||
mongoUrl: MONGOHQ_URL
|
||||
})
|
||||
});
|
||||
|
||||
// Auth0 plugin
|
||||
void fastify.register(fastifyAuth0, {
|
||||
@@ -26,14 +51,13 @@ const start = async () => {
|
||||
audience: AUTH0_AUDIENCE
|
||||
});
|
||||
void fastify.register(jwtAuthz);
|
||||
void fastify.register(sessionAuth);
|
||||
|
||||
void fastify.use('/test', testMiddleware);
|
||||
|
||||
// Hooks
|
||||
void fastify.addHook('preValidation', auth0Verify);
|
||||
|
||||
void fastify.register(dbConnector);
|
||||
void fastify.register(testRoutes);
|
||||
void fastify.register(auth0Routes, { prefix: '/auth0' });
|
||||
|
||||
try {
|
||||
const port = Number(PORT);
|
||||
|
||||
@@ -1,14 +1,5 @@
|
||||
import { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';
|
||||
import type { NextFunction, NextHandleFunction } from '@fastify/middie';
|
||||
|
||||
export async function auth0Verify(
|
||||
this: FastifyInstance,
|
||||
request: FastifyRequest,
|
||||
reply: FastifyReply
|
||||
): Promise<void> {
|
||||
await this.authenticate(request, reply);
|
||||
}
|
||||
|
||||
type MiddieRequest = Parameters<NextHandleFunction>[0];
|
||||
type MiddieResponse = Parameters<NextHandleFunction>[1];
|
||||
|
||||
|
||||
@@ -4,8 +4,11 @@
|
||||
"url": "https://github.com/freeCodeCamp/freeCodeCamp/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fastify/cookie": "^8.3.0",
|
||||
"@fastify/middie": "8.1",
|
||||
"@fastify/mongodb": "6.2.0",
|
||||
"@fastify/session": "^10.1.1",
|
||||
"connect-mongo": "^4.6.0",
|
||||
"fastify": "4.14.0",
|
||||
"fastify-auth0-verify": "^1.0.0",
|
||||
"fastify-plugin": "^4.3.0",
|
||||
|
||||
@@ -25,7 +25,7 @@ SOFTWARE.
|
||||
import { FastifyPluginCallback, FastifyRequest } from 'fastify';
|
||||
import fp from 'fastify-plugin';
|
||||
|
||||
interface UserObject {
|
||||
export interface UserObject {
|
||||
scope?: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { FastifyPluginCallback, onRequestHookHandler } from 'fastify';
|
||||
import fp from 'fastify-plugin';
|
||||
|
||||
const sessionAuth: FastifyPluginCallback = (fastify, _opts, done) => {
|
||||
const authenticateSession: onRequestHookHandler = (req, res, done) => {
|
||||
if (!req.session.user) {
|
||||
res.statusCode = 401;
|
||||
void res.send({ msg: 'Unauthorized' });
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
};
|
||||
|
||||
fastify.decorate('authenticateSession', authenticateSession);
|
||||
|
||||
done();
|
||||
};
|
||||
|
||||
declare module 'fastify' {
|
||||
interface FastifyInstance {
|
||||
authenticateSession: onRequestHookHandler;
|
||||
}
|
||||
}
|
||||
|
||||
export default fp(sessionAuth);
|
||||
@@ -0,0 +1,44 @@
|
||||
import { FastifyPluginCallback } from 'fastify';
|
||||
|
||||
import { AUTH0_DOMAIN } from '../utils/env';
|
||||
|
||||
declare module 'fastify' {
|
||||
interface Session {
|
||||
user: {
|
||||
id: string;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const auth0Routes: FastifyPluginCallback = (fastify, _options, done) => {
|
||||
fastify.addHook('onRequest', fastify.authenticate);
|
||||
const collection = fastify.mongo.db?.collection('user');
|
||||
|
||||
fastify.get('/callback', async (req, _res) => {
|
||||
const auth0Res = await fetch(
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
`https://${AUTH0_DOMAIN}/userinfo`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: req.headers.authorization ?? ''
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (!auth0Res.ok) {
|
||||
fastify.log.error(auth0Res);
|
||||
throw new Error('Invalid Auth0 Access Token');
|
||||
}
|
||||
|
||||
const { email } = (await auth0Res.json()) as { email: string };
|
||||
const user = await collection?.findOne({ email });
|
||||
if (user) {
|
||||
req.session.user = { id: user._id.toString() };
|
||||
} else {
|
||||
const DBRes = await collection?.insertOne({ email });
|
||||
req.session.user = { id: DBRes?.insertedId.toString() ?? '' };
|
||||
}
|
||||
await req.session.save();
|
||||
});
|
||||
done();
|
||||
};
|
||||
+12
-14
@@ -1,30 +1,26 @@
|
||||
import { FastifyPluginCallback, FastifyRequest } from 'fastify';
|
||||
import { ObjectId } from '@fastify/mongodb';
|
||||
import { FastifyPluginCallback } from 'fastify';
|
||||
|
||||
export const testRoutes: FastifyPluginCallback = (fastify, _options, done) => {
|
||||
const collection = fastify.mongo.db?.collection('user');
|
||||
|
||||
fastify.get('/test', async (_request, _reply) => {
|
||||
fastify.addHook('onRequest', fastify.authenticateSession);
|
||||
|
||||
fastify.get('/test', async (request, _reply) => {
|
||||
if (!collection) {
|
||||
return { error: 'No collection' };
|
||||
}
|
||||
const user = await collection?.findOne({ email: 'bar@bar.com' });
|
||||
const userId = new ObjectId(request.session.user.id);
|
||||
const user = await collection?.findOne({ _id: userId });
|
||||
return { user };
|
||||
});
|
||||
|
||||
fastify.put(
|
||||
fastify.put<{ Body: { quincyEmails: boolean } }>(
|
||||
'/update-privacy-terms',
|
||||
{
|
||||
preHandler: [
|
||||
function (
|
||||
req: FastifyRequest<{ Body: { quincyEmails: boolean } }>,
|
||||
_res,
|
||||
done
|
||||
) {
|
||||
void req.jwtAuthz(['write:user'], done);
|
||||
}
|
||||
],
|
||||
schema: {
|
||||
body: {
|
||||
type: 'object',
|
||||
required: ['quincyEmails'],
|
||||
properties: {
|
||||
quincyEmails: { type: 'boolean' }
|
||||
@@ -42,8 +38,10 @@ export const testRoutes: FastifyPluginCallback = (fastify, _options, done) => {
|
||||
sendQuincyEmail: !!quincyEmails
|
||||
};
|
||||
|
||||
const userId = new ObjectId(req.session.user.id);
|
||||
|
||||
return collection
|
||||
?.updateOne({ email: 'bar@bar.com' }, { $set: update })
|
||||
?.updateOne({ _id: userId }, { $set: update })
|
||||
.then(() => {
|
||||
void res.code(200).send({ msg: 'Successfully updated' });
|
||||
})
|
||||
|
||||
@@ -21,6 +21,7 @@ if (error) {
|
||||
assert.ok(process.env.NODE_ENV);
|
||||
assert.ok(process.env.AUTH0_DOMAIN);
|
||||
assert.ok(process.env.AUTH0_AUDIENCE);
|
||||
assert.ok(process.env.SESSION_SECRET);
|
||||
|
||||
if (process.env.NODE_ENV !== 'development') {
|
||||
assert.ok(process.env.PORT);
|
||||
@@ -33,3 +34,4 @@ export const NODE_ENV = process.env.NODE_ENV;
|
||||
export const AUTH0_DOMAIN = process.env.AUTH0_DOMAIN;
|
||||
export const AUTH0_AUDIENCE = process.env.AUTH0_AUDIENCE;
|
||||
export const PORT = process.env.PORT || '3000';
|
||||
export const SESSION_SECRET = process.env.SESSION_SECRET;
|
||||
|
||||
Reference in New Issue
Block a user