import { NodeOAuthClient, NodeSavedSession, NodeSavedState, RuntimeLock, } from "@atproto/oauth-client-node"; import { JoseKey } from "@atproto/jwk-jose"; import { oauth_metadata } from "app/api/oauth/[route]/oauth-metadata"; import { supabaseServerClient } from "supabase/serverClient"; import Client from "ioredis"; import Redlock from "redlock"; export async function createOauthClient() { let keyset = process.env.NODE_ENV === "production" ? await Promise.all([ JoseKey.fromImportable(process.env.JOSE_PRIVATE_KEY_1!), ]) : undefined; let requestLock: RuntimeLock | undefined; if (process.env.NODE_ENV === "production" && process.env.REDIS_URL) { const client = new Client(process.env.REDIS_URL); const redlock = new Redlock([client]); requestLock = async (key, fn) => { // 30 seconds should be enough. Since we will be using one lock per user id // we can be quite liberal with the lock duration here. const lock = await redlock.acquire([key], 45e3); try { return await fn(); } finally { await lock.release(); } }; } return new NodeOAuthClient({ // This object will be used to build the payload of the /client-metadata.json // endpoint metadata, exposing the client metadata to the OAuth server. clientMetadata: oauth_metadata, // Used to authenticate the client to the token endpoint. Will be used to // build the jwks object to be exposed on the "jwks_uri" endpoint. keyset, // Interface to store authorization state data (during authorization flows) stateStore, // Interface to store authenticated session data sessionStore, requestLock, }); } let stateStore = { async set(key: string, state: NodeSavedState): Promise { await supabaseServerClient.from("oauth_state_store").upsert({ key, state }); }, async get(key: string): Promise { let { data } = await supabaseServerClient .from("oauth_state_store") .select("state") .eq("key", key) .single(); return (data?.state as NodeSavedState) || undefined; }, async del(key: string): Promise { await supabaseServerClient .from("oauth_state_store") .delete() .eq("key", key); }, }; let sessionStore = { async set(key: string, session: NodeSavedSession): Promise { await supabaseServerClient .from("oauth_session_store") .upsert({ key, session }); }, async get(key: string): Promise { let { data } = await supabaseServerClient .from("oauth_session_store") .select("session") .eq("key", key) .single(); return (data?.session as NodeSavedSession) || undefined; }, async del(key: string): Promise { await supabaseServerClient .from("oauth_session_store") .delete() .eq("key", key); }, };