Skip to content

Commit

Permalink
fix: improve auth for apps
Browse files Browse the repository at this point in the history
close #40
  • Loading branch information
ahgentil committed Jul 5, 2021
1 parent e8778a9 commit 35e1180
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 23 deletions.
2 changes: 1 addition & 1 deletion development.env
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ JWT_SECRET=9&D*+(D*<MrJ:X%/w2ke5w8U?"Hu,$

TOKEN_BASED_AUTH=true
AUTH_TOKEN_JWT_SECRET=XED{d1@60Aa<9CLw)1?EQ3RQHr.%lS
REFRESH_TOKEN_JWT_SECRET=VpR(79,J4!lC*2'R1_f|CU0*-M]8KJ
AUTH_TOKEN_EXPIRATION_IN_MINUTES=10080
REFRESH_TOKEN_JWT_SECRET=VpR(79,J4!lC*2'R1_f|CU0*-M]8KJ
REFRESH_TOKEN_EXPIRATION_IN_MINUTES=86400

# PostgreSQL connection string
Expand Down
70 changes: 49 additions & 21 deletions src/plugins/auth/auth.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// global
import fs from 'fs';
import path from 'path';
import crypto from 'crypto';
import jwt, { Secret, VerifyOptions, SignOptions } from 'jsonwebtoken';
import { promisify } from 'util';
import { JsonWebTokenError } from 'jsonwebtoken';
Expand All @@ -23,11 +24,11 @@ import { TaskManager as MemberTaskManager } from '../../services/members/task-ma
import { Member } from '../../services/members/interfaces/member';

// local
import { register, login, auth } from './schemas';
import { register, login, auth, mlogin, mauth } from './schemas';
import { AuthPluginOptions } from './interfaces/auth';

const promisifiedJwtVerify = promisify<string, Secret, VerifyOptions, { sub: string }>(jwt.verify);
const promisifiedJwtSign = promisify<{ sub: string }, Secret, SignOptions, string>(jwt.sign);
const promisifiedJwtVerify = promisify<string, Secret, VerifyOptions, { sub: string, challenge?: string }>(jwt.verify);
const promisifiedJwtSign = promisify<{ sub: string, challenge?: string }, Secret, SignOptions, string>(jwt.sign);

const plugin: FastifyPluginAsync<AuthPluginOptions> = async (fastify, options) => {
const {
Expand Down Expand Up @@ -147,6 +148,19 @@ const plugin: FastifyPluginAsync<AuthPluginOptions> = async (fastify, options) =
}
fastify.decorate('generateAuthTokensPair', generateAuthTokensPair);

async function generateLoginLinkAndEmailIt(member, reRegistrationAttempt?, challenge?) {
// generate token with member info and expiration
const token = await promisifiedJwtSign({
sub: member.id,
challenge,
}, JWT_SECRET, { expiresIn: `${LOGIN_TOKEN_EXPIRATION_IN_MINUTES}m` });

const link = `${PROTOCOL}://${EMAIL_LINKS_HOST}${challenge ? '/m' : ''}/auth?t=${token}`;
// don't wait for mailer's response; log error and link if it fails.
fastify.mailer.sendLoginEmail(member, link, reRegistrationAttempt)
.catch(err => log.warn(err, `mailer failed. link: ${link}`));
}

// cookie based auth and api endpoints
fastify.register(async function (fastify) {

Expand Down Expand Up @@ -191,17 +205,6 @@ const plugin: FastifyPluginAsync<AuthPluginOptions> = async (fastify, options) =
}
);

async function generateLoginLinkAndEmailIt(member, reRegistrationAttempt?) {
// generate token with member info and expiration
const token = await promisifiedJwtSign({ sub: member.id }, JWT_SECRET,
{ expiresIn: `${LOGIN_TOKEN_EXPIRATION_IN_MINUTES}m` });

const link = `${PROTOCOL}://${EMAIL_LINKS_HOST}/auth?t=${token}`;
// don't wait for mailer's response; log error and link if it fails.
fastify.mailer.sendLoginEmail(member, link, reRegistrationAttempt)
.catch(err => log.warn(err, `mailer failed. link: ${link}`));
}

// login
fastify.post<{ Body: { email: string } }>(
'/login',
Expand Down Expand Up @@ -269,14 +272,39 @@ const plugin: FastifyPluginAsync<AuthPluginOptions> = async (fastify, options) =

fastify.decorateRequest('memberId', null);

fastify.get<{ Querystring: { t: string } }>(
'/auth',
{ schema: auth },
async (request, reply) => {
const { query: { t: token } } = request;
fastify.post<{ Body: { email: string, challenge: string } }>(
'/login',
{ schema: mlogin },
async ({ body, log }, reply) => {
const { email, challenge } = body;
const task = memberTaskManager.createGetByTask(GRAASP_ACTOR, { email });
task.skipActorChecks = true;
const members = await runner.runSingle(task, log);

if (members.length) {
const member = members[0];
await generateLoginLinkAndEmailIt(member, false, challenge);
} else {
log.warn(`Login attempt with non-existent email '${email}'`);
}

reply.status(204);
}
);

fastify.post<{ Body: { t: string, verifier: string } }>(
'/auth',
{ schema: mauth },
async ({ body: { t: token, verifier } }, reply) => {
try {
const { sub: memberId } = await promisifiedJwtVerify(token, JWT_SECRET, {});
const { sub: memberId, challenge } = await promisifiedJwtVerify(token, JWT_SECRET, {});

const verifierHash = crypto.createHash('sha256').update(verifier).digest('hex');
if (challenge !== verifierHash) {
reply.status(401);
throw new Error('challenge fail');
}

// TODO: should we fetch/test the member from the DB?
return generateAuthTokensPair(memberId);
} catch (error) {
Expand All @@ -289,7 +317,7 @@ const plugin: FastifyPluginAsync<AuthPluginOptions> = async (fastify, options) =
);

fastify.get(
'/auth/refresh',
'/auth/refresh', // there's a hardcoded reference to this path above: "verifyMemberInAuthToken()"
{ preHandler: fastify.verifyBearerAuth },
async ({ memberId }) => generateAuthTokensPair(memberId)
);
Expand Down
28 changes: 27 additions & 1 deletion src/plugins/auth/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,18 @@ const login = {
},
};

const mlogin = {
body: {
type: 'object',
required: ['email', 'challenge'],
properties: {
email: { type: 'string', format: 'email' },
challenge: { type: 'string' }
},
additionalProperties: false
},
};

const auth = {
querystring: {
type: 'object',
Expand All @@ -32,8 +44,22 @@ const auth = {
}
};

const mauth = {
body: {
type: 'object',
required: ['t', 'verifier'],
properties: {
t: { type: 'string' },
verifier: { type: 'string' }
},
additionalProperties: false
}
};

export {
register,
login,
auth
mlogin,
auth,
mauth
};

0 comments on commit 35e1180

Please sign in to comment.