Skip to content

Commit

Permalink
refactor(jwks): move JSON Web Key Set fetcher to own module
Browse files Browse the repository at this point in the history
  • Loading branch information
BastiDood committed Jul 17, 2024
1 parent 05d7740 commit efbef72
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 3 deletions.
71 changes: 71 additions & 0 deletions src/lib/server/email.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { IdToken, TokenResponse } from '$lib/server/models/oauth';
import { parse, pick } from 'valibot';
import { fetchJwks } from '$lib/server/jwks';
import { jwtVerify } from 'jose';

import type { Database } from '$lib/server/database';
import GOOGLE from '$lib/server/env/google';
import { createTransport } from 'nodemailer';

// this function refreshes the access token and updates the db accordingly
async function refreshAccessToken(refreshToken: string, email: string, db: Database) {
const body = new URLSearchParams({
refresh_token: refreshToken,
client_id: GOOGLE.OAUTH_CLIENT_ID,
client_secret: GOOGLE.OAUTH_CLIENT_SECRET,
grant_type: 'refresh_token',
});

const res = await fetch('https://oauth2.googleapis.com/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body,
});

const json = await res.json();

const { id_token, access_token, refresh_token } = parse(TokenResponse, json);
const { payload } = await jwtVerify(id_token, fetchJwks, {
issuer: 'https://accounts.google.com',
audience: GOOGLE.OAUTH_CLIENT_ID,
});

const token = parse(pick(IdToken, ['exp']), payload);

await db.updateDesignatedSender(email, token.exp, access_token);

return await db.getDesignatedSender();
}

// this function sends an email to the provided email address with the given body via nodemailer using the access token of the designated admin sender
export async function sendEmailTo(to: string, subject: string, body: string, db: Database) {
let credentials = await db.getDesignatedSender();
if (!credentials) throw Error();
if (!credentials.refresh_token) throw Error();

if (credentials.expiration < new Date())
credentials = await refreshAccessToken(credentials.refresh_token, credentials.email, db);

if (!credentials) throw Error();

const transporter = createTransport({
host: 'smtp.gmail.com',
port: 465,
secure: true,
auth: {
type: 'OAuth2',
user: credentials.email,
clientId: GOOGLE.OAUTH_CLIENT_ID,
clientSecret: GOOGLE.OAUTH_CLIENT_SECRET,
refreshToken: credentials.refresh_token,
accessToken: credentials.access_token,
},
});

transporter.sendMail({
from: credentials.email,
to,
subject,
text: body,
});
}
2 changes: 2 additions & 0 deletions src/lib/server/jwks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import { createRemoteJWKSet } from 'jose';
export const fetchJwks = createRemoteJWKSet(new URL('https://www.googleapis.com/oauth2/v3/certs'));
5 changes: 2 additions & 3 deletions src/routes/oauth/callback/+server.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { AuthorizationCode, IdToken, TokenResponse } from '$lib/server/models/oauth';
import { createRemoteJWKSet, jwtVerify } from 'jose';
import { error, redirect } from '@sveltejs/kit';
import { ok, strictEqual } from 'node:assert/strict';
import { Buffer } from 'node:buffer';
import GOOGLE from '$lib/server/env/google';
import { fetchJwks } from '$lib/server/jwks';
import { jwtVerify } from 'jose';
import { parse } from 'valibot';

const fetchJwks = createRemoteJWKSet(new URL('https://www.googleapis.com/oauth2/v3/certs'));

export async function GET({ fetch, locals: { db }, cookies, url: { searchParams } }) {
const sid = cookies.get('sid');
if (typeof sid === 'undefined') redirect(302, '/oauth/login/');
Expand Down

0 comments on commit efbef72

Please sign in to comment.