From 1506470d5bc6f29a4c22dc82c36680829c28d910 Mon Sep 17 00:00:00 2001 From: brandon-schabel Date: Thu, 16 Nov 2023 01:05:08 -0700 Subject: [PATCH 1/8] working google oauth --- CREATING_A_MODULE.md | 53 ++++++++++++++ README.md | 11 ++- auth/example/.env.example | 3 + auth/example/google-oauth-server-example.ts | 74 +++++++++++++++++++ auth/example/setup-google-oauth.md | 57 +++++++++++++++ auth/oauth.ts | 78 +++++++++++++++++++++ 6 files changed, 270 insertions(+), 6 deletions(-) create mode 100644 CREATING_A_MODULE.md create mode 100644 auth/example/.env.example create mode 100644 auth/example/google-oauth-server-example.ts create mode 100644 auth/example/setup-google-oauth.md create mode 100644 auth/oauth.ts diff --git a/CREATING_A_MODULE.md b/CREATING_A_MODULE.md new file mode 100644 index 0000000..9b5f1a8 --- /dev/null +++ b/CREATING_A_MODULE.md @@ -0,0 +1,53 @@ +Certainly! Let's create a README document to guide users through the process of creating a module in BNK (Bun Nook Kit). This document will be structured to be user-friendly, informative, and aligned with BNK's principles and coding style. + +--- + +# Creating a Module in BNK (Bun Nook Kit) + +## Overview +This guide provides step-by-step instructions on how to create a new module for the BNK framework. Whether you're adding functionality like OAuth integration or something entirely different, these guidelines will help align the module with BNK's design philosophy and standards to ensure consistency. + +## Prerequisites +- Familiarity with TypeScript and BNK's core concepts. +- Understanding of the problem domain the module will address. + +## Step 1: Research and Requirements Gathering +Before coding, understand the scope and requirements of the module. For instance, if you're building an OAuth module, research the OAuth 2.0 protocol, and identify the primary use cases you want to support. + +## Step 2: Designing the Module +### 2.1 Define the API +Design a clear and intuitive API for the module. Consider the functions and interfaces users will interact with. + +### 2.2 Plan the Architecture +Ensure the module aligns with BNK's architecture. Use factory functions, avoid global state, and adhere to strong typing. + +### 2.3 Security and Performance +Plan for security and performance from the start. This is especially important for modules handling sensitive data or requiring high efficiency. + +## Step 3: Implementation +### 3.1 Setup +Set up the basic structure of the module. Create a new directory and files as needed within the BNK project structure. + +### 3.2 Core Functionality +Develop the core functionality of the module. Keep functions short and focused, and use descriptive names. + +### 3.3 Integration +Ensure that the module integrates seamlessly with other BNK components. + +### 3.4 Error Handling +Implement robust error handling to make the module resilient and reliable. + +## Step 4: Testing +Write comprehensive tests for the module. Cover unit testing for individual functions and integration testing for the module as a whole. + +## Step 5: Documentation +Document the module thoroughly. Include a usage guide, example implementations, and a detailed API reference. + +## Step 6: Community Feedback and Iteration +Release a beta version of the module and encourage feedback from the BNK community. Iterate based on the feedback received. + +## Best Practices +- **Follow BNK's Coding Style**: Adhere to the principles outlined in BNK's coding guidelines, such as using `const`, writing pure functions, and avoiding premature optimization. +- **Use Descriptive Names**: Choose clear and descriptive names for functions, variables, and modules. +- **Write Efficient Code**: Focus on practicality and optimization where necessary. Prioritize clarity and simplicity. + diff --git a/README.md b/README.md index b14dabc..292d492 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,6 @@ Bun Nook Kit (BNK) is a comprehensive toolkit for software development, leveraging the power of Bun and TypeScript. With zero third-party dependencies, strong TypeScript inferencing, and a focus on Web API standards, BNK offers a modular, type-safe, and efficient way to build robust applications. -## [View Modules Docs Site](https://nookit.dev/readme) -#### [Doc Repo](https://github.com/brandon-schabel/bun-nook-kit-docs) ## Quick Start @@ -34,9 +32,10 @@ bun dev Visit `http://localhost:3000` in your browser and you should see Hello world and `http://localhost:3000/json` for the json -## Usage Overview +## [View Modules Docs Site](https://nookit.dev/readme) +### [Doc Repo](https://github.com/brandon-schabel/bun-nook-kit-docs) -### Install +## Bun Nook Kit Installation ```bash bun add bnkit @@ -72,7 +71,7 @@ start() ## Discord Server -Join our [Discord Server]("https://discord.gg/rQyWN7V6"), drop in and ask questions, give feedback or just for a chat! +Join our [Discord Server]("https://discord.gg/rQyWN7V6") https://discord.gg/rQyWN7V6, drop in and ask questions, give feedback or just for a chat! ## Key Highlights @@ -82,7 +81,7 @@ Join our [Discord Server]("https://discord.gg/rQyWN7V6"), drop in and ask questi - **TypeSafe with Strong TypeScript type Inferencing** - Strong types tell you where things are incorrect, strong type inferrence allows you to utilize the advantages of strong types and not having to deal with too much TypeScript. -- **Modular** Everything is built with as little dependency other other modules in the repo, however they still work together. Soon I'll be working on a full stack auth package which will utilize everything from server routes, cookies, database(sqlite). +- **Modular** Everything is built with as little direct dependency on other modules in the repo, however they still work together. Soon I'll be working on a full stack auth package which will utilize everything from server routes, cookies, database(sqlite). - **Builds on Web APIs** Bun itself is built on strong principles of sticking to Web APIs In order to maintain as much comptaibility across various packages, BNK sticks to the fundementals of the webplatform APIs. diff --git a/auth/example/.env.example b/auth/example/.env.example new file mode 100644 index 0000000..5cc4709 --- /dev/null +++ b/auth/example/.env.example @@ -0,0 +1,3 @@ +# rename this to .env anf ollow "Setup google oauth" instructions +GOOGLE_OAUTH_CLIENT_ID="" +GOOGLE_OAUTH_CLIENT_SECRET="" \ No newline at end of file diff --git a/auth/example/google-oauth-server-example.ts b/auth/example/google-oauth-server-example.ts new file mode 100644 index 0000000..c183469 --- /dev/null +++ b/auth/example/google-oauth-server-example.ts @@ -0,0 +1,74 @@ +import type { Routes } from "server"; +import { serverFactory } from "server"; +import { createOAuthHandler, googleProvider } from "../oauth"; + +const oauthHandler = createOAuthHandler(googleProvider); + +const googleClientId = Bun.env.GOOGLE_OAUTH_CLIENT_ID || ""; +const googleClientSecret = Bun.env.GOOGLE_OAUTH_CLIENT_SECRET || ""; + + +console.log({ + googleClientId, + googleClientSecret +}) + +const routes = { + "/login": { + GET: (req) => { + const authUrl = oauthHandler.initiateOAuthFlow({ + clientId: googleClientId, + clientSecret: googleClientSecret, + redirectUri: "http://localhost:3000/callback", + }); + return new Response(null, { + headers: { Location: authUrl }, + status: 302, + }); + }, + }, + "/callback": { + GET: async (req) => { + try { + const host = req.headers.get("host") + // Parse the URL and query parameters + const url = new URL(req.url, `http://${host}`); + const queryParams = new URLSearchParams(url.search); + const code = queryParams.get("code"); + + if (!code) { + return new Response("No code provided in query", { status: 400 }); + } + + const tokenInfo = await oauthHandler.handleRedirect(code, { + clientId: googleClientId, + clientSecret: googleClientSecret, + redirectUri: "http://localhost:3000/callback", + }); + + console.log({ tokenInfo }); + + // Logic after successful authentication + return new Response("Login Successful!"); + } catch (error) { + console.error(error) + return new Response("Authentication failed", { status: 500 }); + } + }, + }, + "/": { + GET: (req) => { + // HTML content for the login page + const htmlContent = `

Login with Google

`; + return new Response(htmlContent, { + headers: { "Content-Type": "text/html" }, + }); + }, + }, +} satisfies Routes; + +const server = serverFactory({ + routes, +}); + +server.start() \ No newline at end of file diff --git a/auth/example/setup-google-oauth.md b/auth/example/setup-google-oauth.md new file mode 100644 index 0000000..7a7fe4c --- /dev/null +++ b/auth/example/setup-google-oauth.md @@ -0,0 +1,57 @@ +### Google OAuth Setup Guide + +#### Step 1: Create a Google Cloud Project + +1. **Go to the Google Cloud Console**: Navigate to the [Google Cloud Console](https://console.cloud.google.com/). + +2. **Create a New Project**: Click on the 'Select a project' dropdown near the top of the page, then click 'New Project'. Give your project a name and click 'Create'. + +#### Step 2: Configure the OAuth Consent Screen + +1. **Open the Credentials Page**: In the Google Cloud Console, navigate to the 'Credentials' page under 'APIs & Services'. + +2. **Configure Consent Screen**: Click on 'Configure Consent Screen'. You'll be asked to choose a user type (choose 'External' for testing purposes). Click 'Create'. + +3. **Fill Out Consent Screen Details**: Enter the necessary information: + - App name + - User support email + - Developer contact email + - Optionally, you can add a logo, application homepage link, privacy policy link, and terms of service link. + +4. **Save and Continue**: After filling out the necessary details, click 'Save and Continue'. + +#### Step 3: Create OAuth 2.0 Credentials + +1. **Create Credentials**: Go back to the 'Credentials' page, and click 'Create Credentials' at the top. Select 'OAuth client ID'. + +2. **Application Type**: Choose the application type that best represents your application. For web applications, select 'Web application'. + +3. **Set Authorized Redirect URIs**: Under 'Authorized redirect URIs', click 'Add URI' and enter the redirect URI your application will use (/callback). This is the URI where Google will send responses to your OAuth requests. + +4. **Create**: Click 'Create'. Google will then generate your client ID and client secret. + +#### Step 4: Note Down Your Credentials + +1. **Copy the Client ID and Client Secret**: Once created, you'll see a dialog showing your client ID and client secret. Copy these values as you will need them to configure your application. + +#### Step 5: Enable Required APIs + +1. **Enable APIs**: If your application requires access to specific Google APIs (like Gmail, Google Calendar, etc.), go to the 'Library' in the Google Cloud Console, search for the necessary APIs, and enable them for your project. + +#### Step 6: Implement OAuth in Your Application + +1. **Use Credentials in Your App**: Use the client ID and client secret in your application's OAuth configuration. + +2. **Handle Redirects**: Make sure your application can handle redirects from Google and can exchange the authorization code for an access token. + +#### Step 7: Testing and Deployment + +1. **Test the OAuth Flow**: Thoroughly test the OAuth flow in your application to ensure everything is working as expected. + +2. **Submit for Verification**: If your application will be used by users outside your organization, you must submit your OAuth consent screen for verification by Google. + +3. **Deploy Your Application**: Once everything is tested and verified, you can deploy your application. + +### Conclusion + +This guide provides a basic overview of setting up Google OAuth for a new application. Depending on your specific needs and the APIs you intend to use, there may be additional configuration steps. Always ensure that you follow best practices for security and user privacy. \ No newline at end of file diff --git a/auth/oauth.ts b/auth/oauth.ts new file mode 100644 index 0000000..1493268 --- /dev/null +++ b/auth/oauth.ts @@ -0,0 +1,78 @@ +// OAuth types +interface OAuthConfig { + clientId: string; + clientSecret: string; + redirectUri: string; + // Additional provider-specific fields +} + +interface OAuthToken { + accessToken: string; + tokenType: string; + expiresIn: number; // Time in seconds after which the token expires + refreshToken?: string; // Optional, not all flows return a refresh token + scope?: string; // Optional, scope of the access granted + idToken?: string; // Optional, used in OpenID Connect (OIDC) + // Additional fields can be added here depending on the OAuth provider +} + +interface OAuthProvider { + getAuthorizationUrl(config: OAuthConfig): string; + getToken(code: string, config: OAuthConfig): Promise; // Simplified for demonstration + +} + +export const createOAuthHandler = (provider: OAuthProvider) => { + const initiateOAuthFlow = (config: OAuthConfig) => { + return provider.getAuthorizationUrl(config); + }; + + const handleRedirect = async (code: string, config: OAuthConfig) => { + return await provider.getToken(code, config); + }; + + return { + initiateOAuthFlow, + handleRedirect, + }; +}; + +export const googleProvider: OAuthProvider = { + getAuthorizationUrl: (config) => { + const baseUrl = "https://accounts.google.com/o/oauth2/v2/auth"; + const queryParams = new URLSearchParams({ + client_id: config.clientId, + redirect_uri: config.redirectUri, + response_type: "code", + scope: "email profile", + }); + return `${baseUrl}?${queryParams.toString()}`; + }, + getToken: async (code, config) => { + const tokenUrl = "https://oauth2.googleapis.com/token"; + const response = await fetch(tokenUrl, { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: new URLSearchParams({ + code: code, + client_id: config.clientId, + client_secret: config.clientSecret, + redirect_uri: config.redirectUri, + grant_type: "authorization_code", + }), + }); + + console.log({ response }); + + if (!response.ok) { + throw new Error(`Failed to fetch token`); + } + + const tokenInfo: OAuthToken = await response.json(); + return tokenInfo; + }, + +}; + From d92e834184d3a3558dc431227f4c48f2f41bc3a5 Mon Sep 17 00:00:00 2001 From: brandon-schabel Date: Thu, 16 Nov 2023 11:55:25 -0700 Subject: [PATCH 2/8] google oauth provider --- auth/oauth-google-provider.ts | 37 +++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 auth/oauth-google-provider.ts diff --git a/auth/oauth-google-provider.ts b/auth/oauth-google-provider.ts new file mode 100644 index 0000000..5729454 --- /dev/null +++ b/auth/oauth-google-provider.ts @@ -0,0 +1,37 @@ +import { OAuthProvider, OAuthToken } from "./oauth-types"; + +export const googleProvider: OAuthProvider = { + getAuthorizationUrl: (config) => { + const baseUrl = "https://accounts.google.com/o/oauth2/v2/auth"; + const queryParams = new URLSearchParams({ + client_id: config.clientId, + redirect_uri: config.redirectUri, + response_type: "code", + scope: "email profile", + }); + return `${baseUrl}?${queryParams.toString()}`; + }, + getToken: async (code, config) => { + const tokenUrl = "https://oauth2.googleapis.com/token"; + const response = await fetch(tokenUrl, { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: new URLSearchParams({ + code: code, + client_id: config.clientId, + client_secret: config.clientSecret, + redirect_uri: config.redirectUri, + grant_type: "authorization_code", + }), + }); + + if (!response.ok) { + throw new Error(`Failed to fetch token`); + } + + const tokenInfo: OAuthToken = await response.json(); + return tokenInfo; + }, +}; From f4500611f0feff0965cf67f165a5eccece0cf407 Mon Sep 17 00:00:00 2001 From: brandon-schabel Date: Fri, 17 Nov 2023 16:55:38 -0700 Subject: [PATCH 3/8] cleaned up oauth module --- auth/example/google-oauth-server-example.ts | 39 +++---- auth/example/setup-google-oauth.md | 57 ---------- auth/example/setup-oauth-providers.md | 81 ++++++++++++++ auth/oauth-google-provider.ts | 72 +++++++------ auth/oauth-microsoft-provider.ts | 44 ++++++++ auth/oauth-types.ts | 25 +++++ auth/oauth.ts | 112 ++++++++------------ release.ts | 9 +- 8 files changed, 256 insertions(+), 183 deletions(-) delete mode 100644 auth/example/setup-google-oauth.md create mode 100644 auth/example/setup-oauth-providers.md create mode 100644 auth/oauth-microsoft-provider.ts create mode 100644 auth/oauth-types.ts diff --git a/auth/example/google-oauth-server-example.ts b/auth/example/google-oauth-server-example.ts index c183469..015457d 100644 --- a/auth/example/google-oauth-server-example.ts +++ b/auth/example/google-oauth-server-example.ts @@ -1,26 +1,25 @@ +import { createOAuthFactory } from "auth/oauth"; +import { initGoogleOAuth } from "auth/oauth-google-provider"; import type { Routes } from "server"; import { serverFactory } from "server"; -import { createOAuthHandler, googleProvider } from "../oauth"; - -const oauthHandler = createOAuthHandler(googleProvider); const googleClientId = Bun.env.GOOGLE_OAUTH_CLIENT_ID || ""; const googleClientSecret = Bun.env.GOOGLE_OAUTH_CLIENT_SECRET || ""; -console.log({ - googleClientId, - googleClientSecret -}) +const googleOAuthConfig = initGoogleOAuth({ + clientId: googleClientId, + clientSecret: googleClientSecret, +}); + +const googleOAuth = createOAuthFactory(googleOAuthConfig); const routes = { "/login": { - GET: (req) => { - const authUrl = oauthHandler.initiateOAuthFlow({ - clientId: googleClientId, - clientSecret: googleClientSecret, - redirectUri: "http://localhost:3000/callback", - }); + GET: () => { + // you could pass a param for the provider + const authUrl = googleOAuth.initiateOAuthFlow(); + return new Response(null, { headers: { Location: authUrl }, status: 302, @@ -30,7 +29,7 @@ const routes = { "/callback": { GET: async (req) => { try { - const host = req.headers.get("host") + const host = req.headers.get("host"); // Parse the URL and query parameters const url = new URL(req.url, `http://${host}`); const queryParams = new URLSearchParams(url.search); @@ -40,24 +39,20 @@ const routes = { return new Response("No code provided in query", { status: 400 }); } - const tokenInfo = await oauthHandler.handleRedirect(code, { - clientId: googleClientId, - clientSecret: googleClientSecret, - redirectUri: "http://localhost:3000/callback", - }); + const tokenInfo = await googleOAuth.handleRedirect(code); console.log({ tokenInfo }); // Logic after successful authentication return new Response("Login Successful!"); } catch (error) { - console.error(error) + console.error(error); return new Response("Authentication failed", { status: 500 }); } }, }, "/": { - GET: (req) => { + GET: () => { // HTML content for the login page const htmlContent = `

Login with Google

`; return new Response(htmlContent, { @@ -71,4 +66,4 @@ const server = serverFactory({ routes, }); -server.start() \ No newline at end of file +server.start(3000); diff --git a/auth/example/setup-google-oauth.md b/auth/example/setup-google-oauth.md deleted file mode 100644 index 7a7fe4c..0000000 --- a/auth/example/setup-google-oauth.md +++ /dev/null @@ -1,57 +0,0 @@ -### Google OAuth Setup Guide - -#### Step 1: Create a Google Cloud Project - -1. **Go to the Google Cloud Console**: Navigate to the [Google Cloud Console](https://console.cloud.google.com/). - -2. **Create a New Project**: Click on the 'Select a project' dropdown near the top of the page, then click 'New Project'. Give your project a name and click 'Create'. - -#### Step 2: Configure the OAuth Consent Screen - -1. **Open the Credentials Page**: In the Google Cloud Console, navigate to the 'Credentials' page under 'APIs & Services'. - -2. **Configure Consent Screen**: Click on 'Configure Consent Screen'. You'll be asked to choose a user type (choose 'External' for testing purposes). Click 'Create'. - -3. **Fill Out Consent Screen Details**: Enter the necessary information: - - App name - - User support email - - Developer contact email - - Optionally, you can add a logo, application homepage link, privacy policy link, and terms of service link. - -4. **Save and Continue**: After filling out the necessary details, click 'Save and Continue'. - -#### Step 3: Create OAuth 2.0 Credentials - -1. **Create Credentials**: Go back to the 'Credentials' page, and click 'Create Credentials' at the top. Select 'OAuth client ID'. - -2. **Application Type**: Choose the application type that best represents your application. For web applications, select 'Web application'. - -3. **Set Authorized Redirect URIs**: Under 'Authorized redirect URIs', click 'Add URI' and enter the redirect URI your application will use (/callback). This is the URI where Google will send responses to your OAuth requests. - -4. **Create**: Click 'Create'. Google will then generate your client ID and client secret. - -#### Step 4: Note Down Your Credentials - -1. **Copy the Client ID and Client Secret**: Once created, you'll see a dialog showing your client ID and client secret. Copy these values as you will need them to configure your application. - -#### Step 5: Enable Required APIs - -1. **Enable APIs**: If your application requires access to specific Google APIs (like Gmail, Google Calendar, etc.), go to the 'Library' in the Google Cloud Console, search for the necessary APIs, and enable them for your project. - -#### Step 6: Implement OAuth in Your Application - -1. **Use Credentials in Your App**: Use the client ID and client secret in your application's OAuth configuration. - -2. **Handle Redirects**: Make sure your application can handle redirects from Google and can exchange the authorization code for an access token. - -#### Step 7: Testing and Deployment - -1. **Test the OAuth Flow**: Thoroughly test the OAuth flow in your application to ensure everything is working as expected. - -2. **Submit for Verification**: If your application will be used by users outside your organization, you must submit your OAuth consent screen for verification by Google. - -3. **Deploy Your Application**: Once everything is tested and verified, you can deploy your application. - -### Conclusion - -This guide provides a basic overview of setting up Google OAuth for a new application. Depending on your specific needs and the APIs you intend to use, there may be additional configuration steps. Always ensure that you follow best practices for security and user privacy. \ No newline at end of file diff --git a/auth/example/setup-oauth-providers.md b/auth/example/setup-oauth-providers.md new file mode 100644 index 0000000..85898d3 --- /dev/null +++ b/auth/example/setup-oauth-providers.md @@ -0,0 +1,81 @@ +# OAuth Setup Instructions + +## Google OAuth Setup + +#### Step 1: Create a Google Cloud Project +- **Access Google Cloud Console**: Go to [Google Cloud Console](https://console.cloud.google.com/). +- **New Project**: Click 'New Project', name it, and create. + +#### Step 2: Configure OAuth Consent Screen +- **Credentials Page**: Navigate to 'Credentials' under 'APIs & Services'. +- **Consent Screen Setup**: Click 'Configure Consent Screen', select 'External', and create. +- **Details**: Enter app name, support email, and developer email. Add optional details like logo and policy links. +- **Save**: Click 'Save and Continue'. + +#### Step 3: Create OAuth 2.0 Credentials +- **Credentials Creation**: Back on 'Credentials' page, select 'Create Credentials' > 'OAuth client ID'. +- **Application Type**: Choose 'Web application'. +- **Redirect URIs**: Add your redirect URI (/callback). +- **Client ID & Secret**: After clicking 'Create', note down the client ID and secret. + +#### Step 4: Enable Required APIs +- **API Library**: In 'Library', search and enable needed Google APIs. + +#### Step 5: Implement OAuth in Your App +- **Integrate Credentials**: Use client ID and secret in your app's OAuth config. +- **Handle Redirects**: Ensure handling of Google's redirects and token exchange. + +#### Step 6: Test and Deploy +- **Testing**: Thoroughly test the OAuth flow. +- **Verification and Deployment**: Submit for verification if needed and deploy. + +This guide provides a condensed overview of setting up Google OAuth. Adapt it based on your specific application needs and always prioritize security and privacy. + +## GitHub OAuth + +1. **Register Your Application**: Go to GitHub's Developer Settings and create a new OAuth application. +2. **Client ID & Secret**: After registration, you'll receive a client ID and client secret. +3. **Authorization URL**: Use `https://github.com/login/oauth/authorize` for user authorization. +4. **Token URL**: Use `https://github.com/login/oauth/access_token` to exchange the code for a token. +5. **Scopes**: Decide on the scopes you need, like `user:email` for email access. +6. **Callback URL**: Set your callback URL that GitHub will redirect to after authentication. + + +#### Don't Forget: + +1. **Submit for Verification**: If your application will be used by users outside your organization, you must submit your OAuth consent screen for verification by Google. + + +## Meta (Facebook) OAuth + +1. **Facebook App**: Create a new app in the Facebook Developer portal. +2. **Client ID & Secret**: Obtain these from your app's settings. +3. **Authorization URL**: Use `https://www.facebook.com/v9.0/dialog/oauth`. +4. **Token URL**: Use `https://graph.facebook.com/v9.0/oauth/access_token`. +5. **Scopes**: Define scopes, such as `email` and `public_profile`. +6. **Callback URL**: Set your redirect URI in the app settings. + +## Twitter OAuth + +1. **Create Twitter App**: Register your app on Twitter Developer Portal. +2. **Keys and Tokens**: You'll get API key, API secret key, Access token, and Access token secret. +3. **Authorization URL**: Use `https://api.twitter.com/oauth/authorize`. +4. **Token URL**: Use `https://api.twitter.com/oauth/access_token`. +5. **Callback URL**: Define your callback URL in the Twitter app settings. + +## Apple OAuth + +1. **Register with Apple Developer**: Create an app in Apple Developer portal. +2. **Service ID & Secret**: Generate a Services ID and a secret key. +3. **Authorization URL**: Use `https://appleid.apple.com/auth/authorize`. +4. **Token URL**: Use `https://appleid.apple.com/auth/token`. +5. **Scopes**: Define necessary scopes like `email` and `name`. +6. **Redirect URIs**: Add your redirect URIs in the Apple Developer console. + +### Implementation Steps: + +1. **Setup OAuth Provider Configuration**: For each service, configure the details in your OAuth setup, similar to how you did with Google. +2. **Redirect to Authorization URL**: On your login page, add buttons for each service that redirects to their respective authorization URL with the necessary query parameters. +3. **Handle Callbacks**: Implement routes in your server to handle the callbacks, exchanging the authorization code for tokens. +4. **User Authentication**: Use the tokens to fetch user details and authenticate or register them in your system. + diff --git a/auth/oauth-google-provider.ts b/auth/oauth-google-provider.ts index 5729454..5cd60ae 100644 --- a/auth/oauth-google-provider.ts +++ b/auth/oauth-google-provider.ts @@ -1,37 +1,45 @@ -import { OAuthProvider, OAuthToken } from "./oauth-types"; +import { getOAuthToken } from "./oauth"; +import { OAuthConfig, OAuthHelpers, OAuthToken } from "./oauth-types"; -export const googleProvider: OAuthProvider = { - getAuthorizationUrl: (config) => { - const baseUrl = "https://accounts.google.com/o/oauth2/v2/auth"; - const queryParams = new URLSearchParams({ - client_id: config.clientId, - redirect_uri: config.redirectUri, - response_type: "code", - scope: "email profile", - }); - return `${baseUrl}?${queryParams.toString()}`; - }, - getToken: async (code, config) => { - const tokenUrl = "https://oauth2.googleapis.com/token"; - const response = await fetch(tokenUrl, { - method: "POST", - headers: { - "Content-Type": "application/x-www-form-urlencoded", - }, - body: new URLSearchParams({ - code: code, +export type OAuthProviderCreds = Pick; + +export const initGoogleOAuth = ({ + clientId, + clientSecret, +}: OAuthProviderCreds): OAuthConfig => { + return { + clientId, + clientSecret, + redirectUri: "http://localhost:3000/callback", + authReqUrl: "https://accounts.google.com/o/oauth2/v2/auth", + tokenUrl: "https://oauth2.googleapis.com/token", + }; +}; + +export const initProvider = (config: OAuthConfig): OAuthHelpers => { + return { + // TODO add options to be able to change response_type/scope, etc + getAuthorizationUrl: (config) => { + const authUrl = config.authReqUrl; + const queryParams = new URLSearchParams({ client_id: config.clientId, - client_secret: config.clientSecret, redirect_uri: config.redirectUri, - grant_type: "authorization_code", - }), - }); - - if (!response.ok) { - throw new Error(`Failed to fetch token`); - } + response_type: "code", + scope: "email profile", + }); - const tokenInfo: OAuthToken = await response.json(); - return tokenInfo; - }, + return `${authUrl}?${queryParams.toString()}`; + }, + getToken: async (code: string) => { + return getOAuthToken({ code, config }).then((response) => { + if (response.error) { + console.error("Error fetching token:", response.error); + throw new Error(response.error); + } else { + console.log("Access Token:", response.accessToken); + return response; + } + }); + }, + }; }; diff --git a/auth/oauth-microsoft-provider.ts b/auth/oauth-microsoft-provider.ts new file mode 100644 index 0000000..c4ba755 --- /dev/null +++ b/auth/oauth-microsoft-provider.ts @@ -0,0 +1,44 @@ +// export const initGoogleOAuth = ({ +// clientId, +// clientSecret, +// }: OAuthProviderCreds): OAuthConfig => { +// return { +// clientId, +// clientSecret, +// redirectUri: "http://localhost:3000/callback", +// authReqUrl: "https://accounts.google.com/o/oauth2/v2/auth", +// tokenUrl: "https://oauth2.googleapis.com/token", +// }; +// }; + + +// todo update to the above API + export const createMicrosoftProvider = ( + config: Omit +): OAuthHelpers => { + return { + getAuthorizationUrl: (config) => { + const microsoftAuthUrl = + "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"; + const queryParams = new URLSearchParams({ + client_id: config.clientId, + redirect_uri: config.redirectUri, + response_type: "code", + scope: "email profile", + }); + + return `${microsoftAuthUrl}?${queryParams.toString()}`; + }, + getToken: async (code: string) => { + return getOAuthToken({ code, config }).then((response) => { + if (response.error) { + console.error("Error fetching token:", response.error); + throw new Error(response.error); + } else { + console.log("Access Token:", response.accessToken); + return response; + } + }); + }, + }; +}; \ No newline at end of file diff --git a/auth/oauth-types.ts b/auth/oauth-types.ts new file mode 100644 index 0000000..a539aa8 --- /dev/null +++ b/auth/oauth-types.ts @@ -0,0 +1,25 @@ +export type OAuthHelpers = { + getAuthorizationUrl(config: OAuthConfig): string; + getToken(code: string, config: OAuthConfig): Promise; // Simplified for demonstration +}; + +export type OAuthConfig = { + clientId: string; + clientSecret: string; + // the server route that handles the redirect from the OAuth provider + redirectUri: string; + // the url that handles the token request from the OAuth provider + tokenUrl: string; + // the server route that handles the token request from the OAuth provider + authReqUrl: string; +}; + +export type OAuthToken = { + accessToken: string; + tokenType: string; + expiresIn: number; // Time in seconds after which the token expires + refreshToken?: string; // Optional, not all flows return a refresh token + scope?: string; // Optional, scope of the access granted + idToken?: string; // Optional, used in OpenID Connect (OIDC) + // Additional fields can be added here depending on the OAuth provider +}; diff --git a/auth/oauth.ts b/auth/oauth.ts index 1493268..9512b65 100644 --- a/auth/oauth.ts +++ b/auth/oauth.ts @@ -1,78 +1,54 @@ -// OAuth types -interface OAuthConfig { - clientId: string; - clientSecret: string; - redirectUri: string; - // Additional provider-specific fields -} +import { initProvider } from "./oauth-google-provider"; +import { OAuthConfig, OAuthToken } from "./oauth-types"; -interface OAuthToken { - accessToken: string; - tokenType: string; - expiresIn: number; // Time in seconds after which the token expires - refreshToken?: string; // Optional, not all flows return a refresh token - scope?: string; // Optional, scope of the access granted - idToken?: string; // Optional, used in OpenID Connect (OIDC) - // Additional fields can be added here depending on the OAuth provider -} +type FetcherResponse = T & { + error?: string; +}; -interface OAuthProvider { - getAuthorizationUrl(config: OAuthConfig): string; - getToken(code: string, config: OAuthConfig): Promise; // Simplified for demonstration - +// Generic OAuth fetcher +export async function oAuthFetcher( + url: string, + params: Record +): Promise> { + const response = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: new URLSearchParams(params).toString(), + }); + + return response.json(); } -export const createOAuthHandler = (provider: OAuthProvider) => { - const initiateOAuthFlow = (config: OAuthConfig) => { - return provider.getAuthorizationUrl(config); +// Wrapper function for getting token +export async function getOAuthToken({ + code, + config, +}: { + code: string; + config: Omit; +}): Promise> { + const params = { + code, + client_id: config.clientId, + client_secret: config.clientSecret, + redirect_uri: config.redirectUri, + grant_type: "authorization_code", }; - const handleRedirect = async (code: string, config: OAuthConfig) => { - return await provider.getToken(code, config); - }; + return oAuthFetcher(config.tokenUrl, params); +} + +export const createOAuthFactory = (config: OAuthConfig) => { + const provider = initProvider(config); return { - initiateOAuthFlow, - handleRedirect, + handleRedirect: async (code: string) => { + return await provider.getToken(code, config); + }, + initiateOAuthFlow: () => { + return provider.getAuthorizationUrl(config); + }, }; }; - -export const googleProvider: OAuthProvider = { - getAuthorizationUrl: (config) => { - const baseUrl = "https://accounts.google.com/o/oauth2/v2/auth"; - const queryParams = new URLSearchParams({ - client_id: config.clientId, - redirect_uri: config.redirectUri, - response_type: "code", - scope: "email profile", - }); - return `${baseUrl}?${queryParams.toString()}`; - }, - getToken: async (code, config) => { - const tokenUrl = "https://oauth2.googleapis.com/token"; - const response = await fetch(tokenUrl, { - method: "POST", - headers: { - "Content-Type": "application/x-www-form-urlencoded", - }, - body: new URLSearchParams({ - code: code, - client_id: config.clientId, - client_secret: config.clientSecret, - redirect_uri: config.redirectUri, - grant_type: "authorization_code", - }), - }); - - console.log({ response }); - - if (!response.ok) { - throw new Error(`Failed to fetch token`); - } - - const tokenInfo: OAuthToken = await response.json(); - return tokenInfo; - }, - -}; - diff --git a/release.ts b/release.ts index 0718af9..323a2bc 100644 --- a/release.ts +++ b/release.ts @@ -1,13 +1,14 @@ import Bun from "bun"; import path from "path"; import { exit } from "process"; -import * as u from "./"; +// import * as u from "./"; import { ulog } from "./utils/ulog"; +import { deploy, npm } from "index"; // run bun test const testProc = Bun.spawnSync(["bun", "test", "--coverage"], {}); -const output = await u.deploy.logStdOutput(testProc); +const output = await deploy.logStdOutput(testProc); if (!output) { ulog("No output"); @@ -20,12 +21,12 @@ const MAX_RETRIES = Number(Bun.env.MAX_PUBLISH_RETRY) || 10; // Define a max num const e = Bun.env; const { npmPublish, setupNpmAuth, updatePackageVersion } = - u.npm.npmReleaseFactory({ + npm.npmReleaseFactory({ maxRetries: MAX_RETRIES, npmToken: NPM_TOKEN, }); -const { commitAndPush, setupGitConfig } = u.deploy.createGitHubActionsFactory({ +const { commitAndPush, setupGitConfig } = deploy.createGitHubActionsFactory({ sshRepoUrl: "git@github.com:brandon-schabel/bun-nook-kit.git", }); From 16ff81f3c7b282d42fe7a5efbf3ffa5aec55cf75 Mon Sep 17 00:00:00 2001 From: brandon-schabel Date: Fri, 17 Nov 2023 17:11:36 -0700 Subject: [PATCH 4/8] simplify oauth configurations --- auth/example/google-oauth-server-example.ts | 2 +- auth/oauth-google-provider.ts | 45 --------------------- auth/oauth-microsoft-provider.ts | 44 -------------------- auth/oauth-providers.ts | 33 +++++++++++++++ auth/oauth-types.ts | 11 +++++ auth/oauth.ts | 35 +++++++++++++++- 6 files changed, 78 insertions(+), 92 deletions(-) delete mode 100644 auth/oauth-google-provider.ts delete mode 100644 auth/oauth-microsoft-provider.ts create mode 100644 auth/oauth-providers.ts diff --git a/auth/example/google-oauth-server-example.ts b/auth/example/google-oauth-server-example.ts index 015457d..1aa44f8 100644 --- a/auth/example/google-oauth-server-example.ts +++ b/auth/example/google-oauth-server-example.ts @@ -1,5 +1,5 @@ import { createOAuthFactory } from "auth/oauth"; -import { initGoogleOAuth } from "auth/oauth-google-provider"; +import { initGoogleOAuth } from "auth/oauth-providers"; import type { Routes } from "server"; import { serverFactory } from "server"; diff --git a/auth/oauth-google-provider.ts b/auth/oauth-google-provider.ts deleted file mode 100644 index 5cd60ae..0000000 --- a/auth/oauth-google-provider.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { getOAuthToken } from "./oauth"; -import { OAuthConfig, OAuthHelpers, OAuthToken } from "./oauth-types"; - -export type OAuthProviderCreds = Pick; - -export const initGoogleOAuth = ({ - clientId, - clientSecret, -}: OAuthProviderCreds): OAuthConfig => { - return { - clientId, - clientSecret, - redirectUri: "http://localhost:3000/callback", - authReqUrl: "https://accounts.google.com/o/oauth2/v2/auth", - tokenUrl: "https://oauth2.googleapis.com/token", - }; -}; - -export const initProvider = (config: OAuthConfig): OAuthHelpers => { - return { - // TODO add options to be able to change response_type/scope, etc - getAuthorizationUrl: (config) => { - const authUrl = config.authReqUrl; - const queryParams = new URLSearchParams({ - client_id: config.clientId, - redirect_uri: config.redirectUri, - response_type: "code", - scope: "email profile", - }); - - return `${authUrl}?${queryParams.toString()}`; - }, - getToken: async (code: string) => { - return getOAuthToken({ code, config }).then((response) => { - if (response.error) { - console.error("Error fetching token:", response.error); - throw new Error(response.error); - } else { - console.log("Access Token:", response.accessToken); - return response; - } - }); - }, - }; -}; diff --git a/auth/oauth-microsoft-provider.ts b/auth/oauth-microsoft-provider.ts deleted file mode 100644 index c4ba755..0000000 --- a/auth/oauth-microsoft-provider.ts +++ /dev/null @@ -1,44 +0,0 @@ -// export const initGoogleOAuth = ({ -// clientId, -// clientSecret, -// }: OAuthProviderCreds): OAuthConfig => { -// return { -// clientId, -// clientSecret, -// redirectUri: "http://localhost:3000/callback", -// authReqUrl: "https://accounts.google.com/o/oauth2/v2/auth", -// tokenUrl: "https://oauth2.googleapis.com/token", -// }; -// }; - - -// todo update to the above API - export const createMicrosoftProvider = ( - config: Omit -): OAuthHelpers => { - return { - getAuthorizationUrl: (config) => { - const microsoftAuthUrl = - "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"; - const queryParams = new URLSearchParams({ - client_id: config.clientId, - redirect_uri: config.redirectUri, - response_type: "code", - scope: "email profile", - }); - - return `${microsoftAuthUrl}?${queryParams.toString()}`; - }, - getToken: async (code: string) => { - return getOAuthToken({ code, config }).then((response) => { - if (response.error) { - console.error("Error fetching token:", response.error); - throw new Error(response.error); - } else { - console.log("Access Token:", response.accessToken); - return response; - } - }); - }, - }; -}; \ No newline at end of file diff --git a/auth/oauth-providers.ts b/auth/oauth-providers.ts new file mode 100644 index 0000000..8e298d9 --- /dev/null +++ b/auth/oauth-providers.ts @@ -0,0 +1,33 @@ +import { OAuthConfig, OAuthProviderFn } from "./oauth-types"; + +export type ProvidersConfig = Record< + string, + Omit +>; + +export const bnkProviders = { + google: { + redirectUri: "http://localhost:3000/callback", // just a default placeholder + authReqUrl: "https://accounts.google.com/o/oauth2/v2/auth", + tokenUrl: "https://oauth2.googleapis.com/token", + }, + microsoft: { + redirectUri: "http://localhost:3000/callback", + authReqUrl: + "https://login.microsoftonline.com/common/oauth2/v2.0/authorize", + tokenUrl: "http://needtofind", + }, +} satisfies ProvidersConfig; + +export const initGoogleOAuth: OAuthProviderFn = ( + { clientId, clientSecret }, + options +) => { + const redirectUrl = options?.redirectUrl; + return { + ...bnkProviders.google, + redirectUri: redirectUrl ? redirectUrl : bnkProviders.google.redirectUri, + clientId, + clientSecret, + }; +}; diff --git a/auth/oauth-types.ts b/auth/oauth-types.ts index a539aa8..2d56e52 100644 --- a/auth/oauth-types.ts +++ b/auth/oauth-types.ts @@ -23,3 +23,14 @@ export type OAuthToken = { idToken?: string; // Optional, used in OpenID Connect (OIDC) // Additional fields can be added here depending on the OAuth provider }; + +export type OAuthProviderOptions = { + redirectUrl: string; +}; + +export type OAuthProviderCreds = Pick; +export type OAuthProviderFn = ( + config: OAuthProviderCreds, + options?: OAuthProviderOptions +) => OAuthConfig; +export type OAuthProviderInitializer = (config: OAuthConfig) => OAuthHelpers; diff --git a/auth/oauth.ts b/auth/oauth.ts index 9512b65..6a1b931 100644 --- a/auth/oauth.ts +++ b/auth/oauth.ts @@ -1,5 +1,8 @@ -import { initProvider } from "./oauth-google-provider"; -import { OAuthConfig, OAuthToken } from "./oauth-types"; +import { + OAuthConfig, + OAuthProviderInitializer, + OAuthToken, +} from "./oauth-types"; type FetcherResponse = T & { error?: string; @@ -52,3 +55,31 @@ export const createOAuthFactory = (config: OAuthConfig) => { }, }; }; + +export const initProvider: OAuthProviderInitializer = (config) => { + return { + // TODO add options to be able to change response_type/scope, etc + getAuthorizationUrl: (config) => { + const authUrl = config.authReqUrl; + const queryParams = new URLSearchParams({ + client_id: config.clientId, + redirect_uri: config.redirectUri, + response_type: "code", + scope: "email profile", + }); + + return `${authUrl}?${queryParams.toString()}`; + }, + getToken: async (code: string) => { + return getOAuthToken({ code, config }).then((response) => { + if (response.error) { + console.error("Error fetching token:", response.error); + throw new Error(response.error); + } else { + console.log("Access Token:", response.accessToken); + return response; + } + }); + }, + }; +}; From cae6fd19cc8a94b387e14c900bb7a406e1dc43bc Mon Sep 17 00:00:00 2001 From: brandon-schabel Date: Fri, 17 Nov 2023 17:43:34 -0700 Subject: [PATCH 5/8] create oauth exports, simplify naming --- auth/example/google-oauth-server-example.ts | 4 ++-- auth/index.ts | 6 ++++- auth/oauth-providers.ts | 10 ++++---- auth/oauth.ts | 26 ++++++++++----------- 4 files changed, 25 insertions(+), 21 deletions(-) diff --git a/auth/example/google-oauth-server-example.ts b/auth/example/google-oauth-server-example.ts index 1aa44f8..7b951c5 100644 --- a/auth/example/google-oauth-server-example.ts +++ b/auth/example/google-oauth-server-example.ts @@ -1,4 +1,4 @@ -import { createOAuthFactory } from "auth/oauth"; +import { oAuthFactory } from "auth/oauth"; import { initGoogleOAuth } from "auth/oauth-providers"; import type { Routes } from "server"; import { serverFactory } from "server"; @@ -12,7 +12,7 @@ const googleOAuthConfig = initGoogleOAuth({ clientSecret: googleClientSecret, }); -const googleOAuth = createOAuthFactory(googleOAuthConfig); +const googleOAuth = oAuthFactory(googleOAuthConfig); const routes = { "/login": { diff --git a/auth/index.ts b/auth/index.ts index e3edcab..626a2e0 100644 --- a/auth/index.ts +++ b/auth/index.ts @@ -2,5 +2,9 @@ export { createSecurityToken, createToken, getTokenExpireEpoch, - verifyToken, + verifyToken } from "./security-token"; + +export { oAuthFactory } from "./oauth"; + +export { initGoogleOAuth, oAuthProviders } from "./oauth-providers"; diff --git a/auth/oauth-providers.ts b/auth/oauth-providers.ts index 8e298d9..cd308b9 100644 --- a/auth/oauth-providers.ts +++ b/auth/oauth-providers.ts @@ -1,11 +1,11 @@ import { OAuthConfig, OAuthProviderFn } from "./oauth-types"; -export type ProvidersConfig = Record< +export type ProvidersConfigRecord = Record< string, Omit >; -export const bnkProviders = { +export const oAuthProviders = { google: { redirectUri: "http://localhost:3000/callback", // just a default placeholder authReqUrl: "https://accounts.google.com/o/oauth2/v2/auth", @@ -17,7 +17,7 @@ export const bnkProviders = { "https://login.microsoftonline.com/common/oauth2/v2.0/authorize", tokenUrl: "http://needtofind", }, -} satisfies ProvidersConfig; +} satisfies ProvidersConfigRecord; export const initGoogleOAuth: OAuthProviderFn = ( { clientId, clientSecret }, @@ -25,8 +25,8 @@ export const initGoogleOAuth: OAuthProviderFn = ( ) => { const redirectUrl = options?.redirectUrl; return { - ...bnkProviders.google, - redirectUri: redirectUrl ? redirectUrl : bnkProviders.google.redirectUri, + ...oAuthProviders.google, + redirectUri: redirectUrl ? redirectUrl : oAuthProviders.google.redirectUri, clientId, clientSecret, }; diff --git a/auth/oauth.ts b/auth/oauth.ts index 6a1b931..4f3968c 100644 --- a/auth/oauth.ts +++ b/auth/oauth.ts @@ -43,19 +43,6 @@ export async function getOAuthToken({ return oAuthFetcher(config.tokenUrl, params); } -export const createOAuthFactory = (config: OAuthConfig) => { - const provider = initProvider(config); - - return { - handleRedirect: async (code: string) => { - return await provider.getToken(code, config); - }, - initiateOAuthFlow: () => { - return provider.getAuthorizationUrl(config); - }, - }; -}; - export const initProvider: OAuthProviderInitializer = (config) => { return { // TODO add options to be able to change response_type/scope, etc @@ -83,3 +70,16 @@ export const initProvider: OAuthProviderInitializer = (config) => { }, }; }; + +export const oAuthFactory = (config: OAuthConfig) => { + const provider = initProvider(config); + + return { + handleRedirect: async (code: string) => { + return await provider.getToken(code, config); + }, + initiateOAuthFlow: () => { + return provider.getAuthorizationUrl(config); + }, + }; +}; From 52cfb7123c58fa3b11a29656a92f7bd9be59ac94 Mon Sep 17 00:00:00 2001 From: brandon-schabel Date: Tue, 21 Nov 2023 21:07:49 -0700 Subject: [PATCH 6/8] improved create fns in sqlite --- sqlite/sqlite-utils/crud-fn-utils.ts | 49 +++++++++++++++++++++------- 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/sqlite/sqlite-utils/crud-fn-utils.ts b/sqlite/sqlite-utils/crud-fn-utils.ts index 7c42125..b889fea 100644 --- a/sqlite/sqlite-utils/crud-fn-utils.ts +++ b/sqlite/sqlite-utils/crud-fn-utils.ts @@ -21,8 +21,12 @@ export function createItem< const valuesArray = Object.values(item); log({ query, valuesArray }); - // Perform the insert operation - db.query(query).run(...valuesArray); + try { + // Perform the insert operation + db.query(query).run(...valuesArray); + } catch (e) { + throw e; + } if (returnInsertedItem) { // Assuming your db instance has a method to get the last inserted row id @@ -30,16 +34,37 @@ export function createItem< // Query to select the last inserted item const selectQuery = `SELECT * FROM ${tableName} WHERE id = last_insert_rowid();`; - const insertedItem = db.query(selectQuery).get() as TranslatedSchema; - log({ selectQuery, lastId: insertedItem.id, insertedItem }); - return insertedItem as TranslatedSchema; + try { + const insertedItem = db.query(selectQuery).get() as TranslatedSchema; + + log({ selectQuery, lastId: insertedItem.id, insertedItem }); + return insertedItem as TranslatedSchema; + } catch (e) { + throw e; + } } // If not returning the inserted item, return an empty array return null; } +export function readFirstItemByKey< + Schema extends SchemaMap, + TranslatedSchema extends SQLiteSchemaInfer = SQLiteSchemaInfer +>( + db: Database, + tableName: string, + log: (msg: any) => void, + key: keyof TranslatedSchema, + value: string | number +): TranslatedSchema { + const queryString = selectItemByKeyQueryString(tableName, String(key)); + log(queryString); + const query = db.prepare(queryString).get(value) as TranslatedSchema; + return query; +} + // Modify the readItems function to include an optional id parameter. export function readItemById< Schema extends SchemaMap, @@ -50,10 +75,11 @@ export function readItemById< log: (msg: any) => void, id: string | number // Add an optional id parameter ): TranslatedSchema { - const query = selectItemByIdQueryString(tableName, id); + const query = selectItemByKeyQueryString(tableName, "id"); log(query); - // Use the ID in the parameterized query to prevent SQL injection. - const data = db.query(query).get({ $id: id }) as TranslatedSchema; + + const data = db.prepare(query).get(id) as TranslatedSchema; + return data; } @@ -104,11 +130,12 @@ export function readItemsWhere< } // In your crud-string-utils file, add a function to create a SQL query string to select by ID. -export function selectItemByIdQueryString( +export function selectItemByKeyQueryString( tableName: string, - id: string | number + key: string + // value: string | number ): string { - return `SELECT * FROM ${tableName} WHERE id = $id;`; + return `SELECT * FROM ${tableName} WHERE ${key} = ?`; } export function readItems< From ccc92ffd256418680588e45336e959499d89d703 Mon Sep 17 00:00:00 2001 From: brandon-schabel Date: Tue, 21 Nov 2023 21:07:55 -0700 Subject: [PATCH 7/8] improved create fns in sqlite --- sqlite/sqlite-table-factory.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sqlite/sqlite-table-factory.ts b/sqlite/sqlite-table-factory.ts index 2c71840..b710075 100644 --- a/sqlite/sqlite-table-factory.ts +++ b/sqlite/sqlite-table-factory.ts @@ -3,6 +3,7 @@ import { SQLiteSchemaInfer, SchemaMap } from "./sqlite-factory"; import { createItem, deleteItemById, + readFirstItemByKey, readItemById, readItems, readItemsWhere, @@ -62,6 +63,15 @@ export function sqliteTableFactory< readById(id: string | number) { return readItemById(db, tableName, log, id); }, + readItemByKey(key: string, value: string | number) { + return readFirstItemByKey( + db, + tableName, + log, + key, + value + ) as unknown as TranslatedSchema; + }, update(id: string | number, item: Partial>) { return updateItem(db, tableName, log, id, item); }, From 50f38fe7c938712ed495e8581b13ab53acb06725 Mon Sep 17 00:00:00 2001 From: brandon-schabel Date: Tue, 21 Nov 2023 21:08:40 -0700 Subject: [PATCH 8/8] fix charset bug in htmlody response, add more css grid utils --- auth/example/google-oauth-server-example.ts | 2 +- auth/oauth.ts | 24 ++++++++++++--------- data-gen/object-gen.test.ts | 1 - data-gen/object-gen.ts | 13 +++++------ htmlody/css-engine.ts | 9 ++++++++ htmlody/json-to-html-engine.ts | 3 +-- 6 files changed, 32 insertions(+), 20 deletions(-) diff --git a/auth/example/google-oauth-server-example.ts b/auth/example/google-oauth-server-example.ts index 7b951c5..5fe0dc9 100644 --- a/auth/example/google-oauth-server-example.ts +++ b/auth/example/google-oauth-server-example.ts @@ -47,7 +47,7 @@ const routes = { return new Response("Login Successful!"); } catch (error) { console.error(error); - return new Response("Authentication failed", { status: 500 }); + return new Response("Authentication failed", { status: 403 }); } }, }, diff --git a/auth/oauth.ts b/auth/oauth.ts index 4f3968c..0a1f9c1 100644 --- a/auth/oauth.ts +++ b/auth/oauth.ts @@ -27,30 +27,34 @@ export async function oAuthFetcher( // Wrapper function for getting token export async function getOAuthToken({ code, - config, + config: { clientId, clientSecret, redirectUri, tokenUrl }, }: { code: string; config: Omit; }): Promise> { const params = { code, - client_id: config.clientId, - client_secret: config.clientSecret, - redirect_uri: config.redirectUri, + client_id: clientId, + client_secret: clientSecret, + redirect_uri: redirectUri, grant_type: "authorization_code", }; - return oAuthFetcher(config.tokenUrl, params); + return oAuthFetcher(tokenUrl, params); } -export const initProvider: OAuthProviderInitializer = (config) => { +export const initProvider: OAuthProviderInitializer = ({ + clientId, + authReqUrl, + redirectUri, +}) => { return { // TODO add options to be able to change response_type/scope, etc - getAuthorizationUrl: (config) => { - const authUrl = config.authReqUrl; + getAuthorizationUrl: () => { + const authUrl = authReqUrl; const queryParams = new URLSearchParams({ - client_id: config.clientId, - redirect_uri: config.redirectUri, + client_id: clientId, + redirect_uri: redirectUri, response_type: "code", scope: "email profile", }); diff --git a/data-gen/object-gen.test.ts b/data-gen/object-gen.test.ts index 48e3067..9104f81 100644 --- a/data-gen/object-gen.test.ts +++ b/data-gen/object-gen.test.ts @@ -87,4 +87,3 @@ describe("inferTypeAndGenerate", () => { } }); }); - diff --git a/data-gen/object-gen.ts b/data-gen/object-gen.ts index 44a8248..d11ee35 100644 --- a/data-gen/object-gen.ts +++ b/data-gen/object-gen.ts @@ -7,11 +7,11 @@ interface DataGenerators { date: (generator?: DataGenerator) => Date; object: >( shape: T, - generatorMap?: Partial>> + generatorMap?: Partial>>, ) => DataGenerator; array: ( generator: DataGenerator, - length?: number + length?: number, ) => DataGenerator; } @@ -22,7 +22,8 @@ export const dataGenerators: DataGenerators = { date: (generator = () => new Date()) => generator(), object: >( shape: T, - generatorMap: Partial>> = {})=>{ + generatorMap: Partial>> = {}, + ) => { return () => { const result = {} as any; for (const key in shape) { @@ -36,7 +37,7 @@ export const dataGenerators: DataGenerators = { return result as T; }; }, - array: (generator: DataGenerator, length = 10) => { + array: (generator: DataGenerator, length = 10) => { return () => { const result = []; for (let i = 0; i < length; i++) { @@ -47,7 +48,7 @@ export const dataGenerators: DataGenerators = { }, }; -export const inferTypeAndGenerate = (value: Val): any => { +export const inferTypeAndGenerate = (value: Val): any => { switch (typeof value) { case "string": return dataGenerators.string(); @@ -64,7 +65,7 @@ export const inferTypeAndGenerate = (value: Val): any => { const inferredTypeGenerator = inferTypeAndGenerate(value[0]); return dataGenerators.array( () => inferredTypeGenerator, - value.length + value.length, )(); } else if (typeof value === "object" && value !== null) { return dataGenerators.object(value as Record)(); diff --git a/htmlody/css-engine.ts b/htmlody/css-engine.ts index 7011d5f..dd96157 100644 --- a/htmlody/css-engine.ts +++ b/htmlody/css-engine.ts @@ -846,6 +846,15 @@ export const CSS_MAP = { "grid-cols-2": "grid-template-columns: repeat(2, minmax(0, 1fr));", "grid-cols-3": "grid-template-columns: repeat(3, minmax(0, 1fr));", "grid-cols-4": "grid-template-columns: repeat(4, minmax(0, 1fr));", + "grid-cols-5": "grid-template-columns: repeat(5, minmax(0, 1fr));", + "grid-cols-6": "grid-template-columns: repeat(6, minmax(0, 1fr));", + "grid-cols-7": "grid-template-columns: repeat(7, minmax(0, 1fr));", + "grid-cols-8": "grid-template-columns: repeat(8, minmax(0, 1fr));", + "grid-cols-9": "grid-template-columns: repeat(9, minmax(0, 1fr));", + "grid-cols-10": "grid-template-columns: repeat(10, minmax(0, 1fr));", + "grid-cols-11": "grid-template-columns: repeat(11, minmax(0, 1fr));", + "grid-cols-12": "grid-template-columns: repeat(12, minmax(0, 1fr));", + // Text Utilities "text-left": textAlign("left"), diff --git a/htmlody/json-to-html-engine.ts b/htmlody/json-to-html-engine.ts index e3e98fd..64a0801 100644 --- a/htmlody/json-to-html-engine.ts +++ b/htmlody/json-to-html-engine.ts @@ -57,7 +57,6 @@ export function renderHtmlTag({ validate?: boolean; }): string { if (validate) { - console.log("validating tag name", tagName) validateTagName(tagName); } @@ -410,7 +409,7 @@ export const htmlodyBuilder = < const html = buildHtmlDoc(bodyConfig, options); return new Response(html, { headers: { - "Content-Type": "text/html", + "Content-Type": "text/html; charset=utf-8", }, }); };