From 5be2df1f0ba43e384ab21f68646713728e3f7c09 Mon Sep 17 00:00:00 2001 From: Ardis Lu Date: Tue, 19 Jul 2022 23:23:47 -0700 Subject: [PATCH] Add Atom feed --- functions/api.js | 61 +------------------------------------------ functions/atom.xml.js | 41 +++++++++++++++++++++++++++++ functions/auth.js | 60 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+), 60 deletions(-) create mode 100644 functions/atom.xml.js create mode 100644 functions/auth.js diff --git a/functions/api.js b/functions/api.js index dcca070..7be2d6e 100644 --- a/functions/api.js +++ b/functions/api.js @@ -1,63 +1,4 @@ -function base64UrlEncode(s) { - return btoa(s) - .replaceAll('=', '') - .replaceAll('+', '-') - .replaceAll('/', '_'); -} - -async function sign(message, privateKey) { - const cleanedKey = privateKey - .replace('-----BEGIN PRIVATE KEY-----', '') - .replace('-----END PRIVATE KEY-----', '') - .replaceAll(/\r\n|\n|\r/g, ''); - const keyBuffer = Uint8Array.from(atob(cleanedKey), c => c.charCodeAt(0)); - - const signingKey = await crypto.subtle.importKey( - 'pkcs8', - keyBuffer, - { - name: 'RSASSA-PKCS1-V1_5', - hash: 'SHA-256' - }, - false, - ['sign'] - ); - - const signatureBuffer = await crypto.subtle.sign('RSASSA-PKCS1-V1_5', signingKey, new TextEncoder().encode(message)); - return base64UrlEncode(String.fromCharCode(...new Uint8Array(signatureBuffer))); -} - -async function getGoogleAuthToken(clientEmail, privateKey, scopes) { - const header = base64UrlEncode(JSON.stringify({ - alg: 'RS256', - typ: 'JWT' - })); - - const now = Math.round(Date.now() / 1000); - const expires = now + 3600; - const payload = base64UrlEncode(JSON.stringify({ - iss: clientEmail, - aud: 'https://oauth2.googleapis.com/token', - exp: expires, - iat: now, - scope: scopes - })); - - const unsignedJwt = `${header}.${payload}`; - const signature = await sign(unsignedJwt, privateKey); - const jwt = `${unsignedJwt}.${signature}`; - - const response = await fetch('https://oauth2.googleapis.com/token', { - method: 'POST', - body: JSON.stringify({ - assertion: jwt, - grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer' - }) - }); - const oauth = await response.json(); - - return oauth.access_token; -} +import { getGoogleAuthToken } from './auth.js'; export async function onRequestGet({ request, env }) { const url = new URL(request.url); diff --git a/functions/atom.xml.js b/functions/atom.xml.js new file mode 100644 index 0000000..bd6cc58 --- /dev/null +++ b/functions/atom.xml.js @@ -0,0 +1,41 @@ +import { getGoogleAuthToken } from './auth.js'; + +export async function onRequestGet({ env }) { + const entries = []; + const updatedDates = []; + const token = await getGoogleAuthToken(env.EMAIL, env.PRIVATE_KEY, env.SCOPES); + const sheetUrl = 'https://sheets.googleapis.com/v4/spreadsheets/1pfGF8yBu3D0GPTezygLuzu3Cif8SkjhtG98nL-czlhc/values/B3:I'; + await fetch(sheetUrl, { headers: { Authorization: `Bearer ${token}` } }) + .then(r => r.json()) + .then(googleSheet => { + for (const article of googleSheet.values) { + entries.push(` + urn:google.com:${article[2]} + ${article[0]} + ${article[1]} + + ${new Date(article[5]).toISOString()} + ${new Date(article[6]).toISOString()} + `); + updatedDates.push(new Date(article[6])); + } + }); + + const feed = ` + + + https://ardislu.dev/ + ardislu.dev + Notes on web development, crypto, self-hosting, and tech in general. + + Ardis Lu + + + ${new Date(Math.max(...updatedDates)).toISOString()} + + ${entries.join('')} + + ` + + return new Response(feed, { headers: { 'Content-Type': 'application/atom+xml' } }); +} \ No newline at end of file diff --git a/functions/auth.js b/functions/auth.js new file mode 100644 index 0000000..a6c8a3d --- /dev/null +++ b/functions/auth.js @@ -0,0 +1,60 @@ +function base64UrlEncode(s) { + return btoa(s) + .replaceAll('=', '') + .replaceAll('+', '-') + .replaceAll('/', '_'); +} + +async function sign(message, privateKey) { + const cleanedKey = privateKey + .replace('-----BEGIN PRIVATE KEY-----', '') + .replace('-----END PRIVATE KEY-----', '') + .replaceAll(/\r\n|\n|\r/g, ''); + const keyBuffer = Uint8Array.from(atob(cleanedKey), c => c.charCodeAt(0)); + + const signingKey = await crypto.subtle.importKey( + 'pkcs8', + keyBuffer, + { + name: 'RSASSA-PKCS1-V1_5', + hash: 'SHA-256' + }, + false, + ['sign'] + ); + + const signatureBuffer = await crypto.subtle.sign('RSASSA-PKCS1-V1_5', signingKey, new TextEncoder().encode(message)); + return base64UrlEncode(String.fromCharCode(...new Uint8Array(signatureBuffer))); +} + +export async function getGoogleAuthToken(clientEmail, privateKey, scopes) { + const header = base64UrlEncode(JSON.stringify({ + alg: 'RS256', + typ: 'JWT' + })); + + const now = Math.round(Date.now() / 1000); + const expires = now + 3600; + const payload = base64UrlEncode(JSON.stringify({ + iss: clientEmail, + aud: 'https://oauth2.googleapis.com/token', + exp: expires, + iat: now, + scope: scopes + })); + + const unsignedJwt = `${header}.${payload}`; + const signature = await sign(unsignedJwt, privateKey); + const jwt = `${unsignedJwt}.${signature}`; + + const response = await fetch('https://oauth2.googleapis.com/token', { + method: 'POST', + body: JSON.stringify({ + assertion: jwt, + grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer' + }) + }); + const oauth = await response.json(); + + return oauth.access_token; +} \ No newline at end of file -- 2.47.3