mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-04-14 07:00:51 -04:00
refactor: separate getAuthedUser from authorize (#66842)
Co-authored-by: Ahmad Abdolsaheb <ahmad.abdolsaheb@gmail.com>
This commit is contained in:
committed by
GitHub
parent
d69f24b31b
commit
06c40cc23f
@@ -238,6 +238,147 @@ describe('auth', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('req.getAuthedUser', () => {
|
||||
test('returns message when access token is missing', async () => {
|
||||
fastify.get('/test', async req => {
|
||||
return req.getAuthedUser();
|
||||
});
|
||||
|
||||
const res = await fastify.inject({
|
||||
method: 'GET',
|
||||
url: '/test'
|
||||
});
|
||||
|
||||
expect(res.json()).toEqual({
|
||||
message: 'Access token is required for this request'
|
||||
});
|
||||
});
|
||||
|
||||
test('returns message when access token is not signed', async () => {
|
||||
fastify.get('/test', async req => {
|
||||
return req.getAuthedUser();
|
||||
});
|
||||
|
||||
const token = jwt.sign(
|
||||
{ accessToken: createAccessToken('123') },
|
||||
JWT_SECRET
|
||||
);
|
||||
|
||||
const res = await fastify.inject({
|
||||
method: 'GET',
|
||||
url: '/test',
|
||||
cookies: {
|
||||
jwt_access_token: token
|
||||
}
|
||||
});
|
||||
|
||||
expect(res.json()).toEqual({
|
||||
message: 'Access token is required for this request'
|
||||
});
|
||||
});
|
||||
|
||||
test('returns message when access token is invalid', async () => {
|
||||
fastify.get('/test', async req => {
|
||||
return req.getAuthedUser();
|
||||
});
|
||||
|
||||
const token = jwt.sign(
|
||||
{ accessToken: createAccessToken('123') },
|
||||
'invalid-secret'
|
||||
);
|
||||
|
||||
const res = await fastify.inject({
|
||||
method: 'GET',
|
||||
url: '/test',
|
||||
cookies: {
|
||||
jwt_access_token: signCookie(token)
|
||||
}
|
||||
});
|
||||
|
||||
expect(res.json()).toEqual({
|
||||
message: 'Your access token is invalid'
|
||||
});
|
||||
});
|
||||
|
||||
test('returns message when access token has expired', async () => {
|
||||
fastify.get('/test', async req => {
|
||||
return req.getAuthedUser();
|
||||
});
|
||||
|
||||
const token = jwt.sign(
|
||||
{ accessToken: createAccessToken('123', -1) },
|
||||
JWT_SECRET
|
||||
);
|
||||
|
||||
const res = await fastify.inject({
|
||||
method: 'GET',
|
||||
url: '/test',
|
||||
cookies: {
|
||||
jwt_access_token: signCookie(token)
|
||||
}
|
||||
});
|
||||
|
||||
expect(res.json()).toEqual({
|
||||
message: 'Access token is no longer valid'
|
||||
});
|
||||
});
|
||||
|
||||
test('returns message when user is not found', async () => {
|
||||
// @ts-expect-error prisma isn't defined, since we're not building the
|
||||
// full application here.
|
||||
fastify.prisma = { user: { findUnique: () => null } };
|
||||
|
||||
fastify.get('/test', async req => {
|
||||
return req.getAuthedUser();
|
||||
});
|
||||
|
||||
const token = jwt.sign(
|
||||
{ accessToken: createAccessToken('123') },
|
||||
JWT_SECRET
|
||||
);
|
||||
|
||||
const res = await fastify.inject({
|
||||
method: 'GET',
|
||||
url: '/test',
|
||||
cookies: {
|
||||
jwt_access_token: signCookie(token)
|
||||
}
|
||||
});
|
||||
|
||||
expect(res.json()).toEqual({
|
||||
message: 'Your access token is invalid'
|
||||
});
|
||||
});
|
||||
|
||||
test('returns user when token is valid', async () => {
|
||||
const fakeUser = { id: '123', username: 'test-user' };
|
||||
// @ts-expect-error prisma isn't defined, since we're not building the
|
||||
// full application here.
|
||||
fastify.prisma = { user: { findUnique: () => fakeUser } };
|
||||
|
||||
fastify.get('/test', async req => {
|
||||
return req.getAuthedUser();
|
||||
});
|
||||
|
||||
const token = jwt.sign(
|
||||
{ accessToken: createAccessToken('123') },
|
||||
JWT_SECRET
|
||||
);
|
||||
|
||||
const res = await fastify.inject({
|
||||
method: 'GET',
|
||||
url: '/test',
|
||||
cookies: {
|
||||
jwt_access_token: signCookie(token)
|
||||
}
|
||||
});
|
||||
|
||||
expect(res.json()).toEqual({
|
||||
user: fakeUser
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('onRequest Hook', () => {
|
||||
test('should update the jwt_access_token to httpOnly and secure', async () => {
|
||||
const rawValue = 'should-not-change';
|
||||
|
||||
@@ -7,6 +7,16 @@ import { JWT_SECRET } from '../utils/env.js';
|
||||
import { type Token, isExpired } from '../utils/tokens.js';
|
||||
import { ERRORS } from '../exam-environment/utils/errors.js';
|
||||
|
||||
type AuthResult =
|
||||
| {
|
||||
message: string;
|
||||
user?: never;
|
||||
}
|
||||
| {
|
||||
message?: never;
|
||||
user: user;
|
||||
};
|
||||
|
||||
declare module 'fastify' {
|
||||
interface FastifyReply {
|
||||
setAccessTokenCookie: (this: FastifyReply, accessToken: Token) => void;
|
||||
@@ -16,6 +26,7 @@ declare module 'fastify' {
|
||||
// TODO: is the full user the correct type here?
|
||||
user: user | null;
|
||||
accessDeniedMessage: { type: 'info'; content: string } | null;
|
||||
getAuthedUser: () => Promise<AuthResult>;
|
||||
}
|
||||
|
||||
interface FastifyInstance {
|
||||
@@ -60,26 +71,26 @@ const auth: FastifyPluginCallback = (fastify, _options, done) => {
|
||||
const setAccessDenied = (req: FastifyRequest, content: string) =>
|
||||
(req.accessDeniedMessage = { type: 'info', content });
|
||||
|
||||
const handleAuth = async (req: FastifyRequest): Promise<void> => {
|
||||
const tokenCookie = req.cookies.jwt_access_token;
|
||||
if (!tokenCookie) return void setAccessDenied(req, TOKEN_REQUIRED);
|
||||
async function getAuthedUser(this: FastifyRequest): Promise<AuthResult> {
|
||||
const tokenCookie = this.cookies.jwt_access_token;
|
||||
if (!tokenCookie) return { message: TOKEN_REQUIRED };
|
||||
|
||||
const unsignedToken = req.unsignCookie(tokenCookie);
|
||||
if (!unsignedToken.valid) return void setAccessDenied(req, TOKEN_REQUIRED);
|
||||
const unsignedToken = this.unsignCookie(tokenCookie);
|
||||
if (!unsignedToken.valid) return { message: TOKEN_REQUIRED };
|
||||
|
||||
const jwtAccessToken = unsignedToken.value;
|
||||
|
||||
try {
|
||||
jwt.verify(jwtAccessToken, JWT_SECRET);
|
||||
} catch {
|
||||
return void setAccessDenied(req, TOKEN_INVALID);
|
||||
return { message: TOKEN_INVALID };
|
||||
}
|
||||
|
||||
const { accessToken } = jwt.decode(jwtAccessToken) as {
|
||||
accessToken: Token;
|
||||
};
|
||||
|
||||
if (isExpired(accessToken)) return void setAccessDenied(req, TOKEN_EXPIRED);
|
||||
if (isExpired(accessToken)) return { message: TOKEN_EXPIRED };
|
||||
// We're using token.userId since it's possible for the user record to be
|
||||
// malformed and for prisma to throw while trying to find the user.
|
||||
fastify.Sentry?.setUser({
|
||||
@@ -89,8 +100,20 @@ const auth: FastifyPluginCallback = (fastify, _options, done) => {
|
||||
const user = await fastify.prisma.user.findUnique({
|
||||
where: { id: accessToken.userId }
|
||||
});
|
||||
if (!user) return void setAccessDenied(req, TOKEN_INVALID);
|
||||
req.user = user;
|
||||
|
||||
return user ? { user } : { message: TOKEN_INVALID };
|
||||
}
|
||||
|
||||
fastify.decorateRequest('getAuthedUser', getAuthedUser);
|
||||
|
||||
const handleAuth = async (req: FastifyRequest): Promise<void> => {
|
||||
const { message, user } = await req.getAuthedUser();
|
||||
|
||||
if (user) {
|
||||
req.user = user;
|
||||
} else {
|
||||
setAccessDenied(req, message);
|
||||
}
|
||||
};
|
||||
|
||||
async function handleExamEnvironmentTokenAuth(
|
||||
|
||||
Reference in New Issue
Block a user