From ca4b35400383afc76727d3fc6f05c4e478dbb579 Mon Sep 17 00:00:00 2001 From: Nicky Date: Tue, 13 Aug 2024 18:58:34 +0100 Subject: [PATCH] create OAuthClientRepository + client utils --- packages/api/src/oauth2/index.ts | 41 ++++++++++++++++++ packages/api/src/oauth2/types.ts | 4 ++ packages/api/src/oauth2/utils.ts | 74 ++++++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+) create mode 100644 packages/api/src/oauth2/index.ts create mode 100644 packages/api/src/oauth2/types.ts create mode 100644 packages/api/src/oauth2/utils.ts diff --git a/packages/api/src/oauth2/index.ts b/packages/api/src/oauth2/index.ts new file mode 100644 index 000000000..3b9cc2dc0 --- /dev/null +++ b/packages/api/src/oauth2/index.ts @@ -0,0 +1,41 @@ +import type { GrantIdentifier, OAuthClient, OAuthClientRepository } from '@jmondi/oauth2-server' +import { OAuthClientUtils } from 'src/oauth2/utils' + +class SendOAuthClientRepository implements OAuthClientRepository { + /** + * Gets client from client id + * @param {string} identifier - client identifier (`client_id`) + * @returns {Promise} - OAuthClient object + */ + async getByIdentifier(identifier: string): Promise { + return await OAuthClientUtils.getClient(identifier) + } + + /** + * Checks if the client is valid. The client is valid if: + * - The client exists + * - AND the client is authorized for the requested grant type + * @param {GrantIdentifier} grantType - requested grant type + * @param {OAuthClient} client - client + * @param {string | undefined} clientSecret - client secret (not required as we are using PKCE) @see {https://tsoauth2server.com/docs/grants/authorization_code} + * @returns {Promise} + */ + async isClientValid( + grantType: GrantIdentifier, + client: OAuthClient, + clientSecret?: string + ): Promise { + const clientExists: boolean = await OAuthClientUtils.clientExists(client.id) + if (!clientExists) { + return false + } + const clientHasAuthorizationGrantType: boolean = await OAuthClientUtils.clientHasGrantType( + client.id, + grantType + ) + if (!clientHasAuthorizationGrantType) { + return false + } + return true + } +} diff --git a/packages/api/src/oauth2/types.ts b/packages/api/src/oauth2/types.ts new file mode 100644 index 000000000..45101c957 --- /dev/null +++ b/packages/api/src/oauth2/types.ts @@ -0,0 +1,4 @@ +export interface OAuthScope { + name: string + [key: string]: string +} diff --git a/packages/api/src/oauth2/utils.ts b/packages/api/src/oauth2/utils.ts new file mode 100644 index 000000000..a15e751d2 --- /dev/null +++ b/packages/api/src/oauth2/utils.ts @@ -0,0 +1,74 @@ +import type { GrantIdentifier, OAuthClient } from '@jmondi/oauth2-server' +import { supabaseAdmin } from 'app/utils/supabase/admin' +import debug from 'debug' +import type { OAuthScope } from 'src/oauth2/types' + +const logger = debug.log + +export const getClient = async (clientId: string): Promise => { + try { + const { data, error } = await supabaseAdmin + .from('oauth2_clients') + .select('*') + .eq('client_id', clientId) + .single() + if (error) { + logger(error) + throw error + } + return { + id: data.client_id, + name: data.client_name, + redirectUris: [data.redirect_uri], + allowedGrants: await getClientGrants(clientId), + scopes: await getClientScopes(clientId), + } + } catch (error) { + logger(`Error retrieving client from client id: [${clientId}]. ${error})`) + throw error + } +} +export const clientExists = async (clientId: string): Promise => { + const { data, error } = await supabaseAdmin + .from('oauth2_clients') + .select('*') + .eq('client_id', clientId) + .single() + if (error) { + logger(`Unable to determine whether client exists. clientId: [${clientId}]. ${error}`) + return false + } + return !!data +} + +export const clientHasGrantType = async ( + clientId: string, + grantType: GrantIdentifier +): Promise => { + const clientGrants: GrantIdentifier[] = await getClientGrants(clientId) + return clientGrants.includes(grantType) +} + +export const getClientGrants = async (clientId: string): Promise => { + const { data, error } = await supabaseAdmin + .from('oauth2_client_authorization_grant_types') + .select('grant_type') + .eq('client_id', clientId) + if (error) { + logger(error) + throw error + } + return data.map((grant) => grant.grant_type) as GrantIdentifier[] +} + +export const getClientScopes = async (clientId: string): Promise => { + const { data, error } = await supabaseAdmin + .from('oauth2_client_scopes') + .select('*') + .eq('client_id', clientId) + if (error) { + logger(error) + return [] + } + return data +}