Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Oauth Implementation (Google preconfigured) #58

Merged
merged 8 commits into from
Nov 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions CREATING_A_MODULE.md
Original file line number Diff line number Diff line change
@@ -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.

11 changes: 5 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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

Expand All @@ -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.

Expand Down
3 changes: 3 additions & 0 deletions auth/example/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# rename this to .env anf ollow "Setup google oauth" instructions
GOOGLE_OAUTH_CLIENT_ID=""
GOOGLE_OAUTH_CLIENT_SECRET=""
69 changes: 69 additions & 0 deletions auth/example/google-oauth-server-example.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { oAuthFactory } from "auth/oauth";
import { initGoogleOAuth } from "auth/oauth-providers";
import type { Routes } from "server";
import { serverFactory } from "server";

const googleClientId = Bun.env.GOOGLE_OAUTH_CLIENT_ID || "";
const googleClientSecret = Bun.env.GOOGLE_OAUTH_CLIENT_SECRET || "";


const googleOAuthConfig = initGoogleOAuth({
clientId: googleClientId,
clientSecret: googleClientSecret,
});

const googleOAuth = oAuthFactory(googleOAuthConfig);

const routes = {
"/login": {
GET: () => {
// you could pass a param for the provider
const authUrl = googleOAuth.initiateOAuthFlow();

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 googleOAuth.handleRedirect(code);

console.log({ tokenInfo });

// Logic after successful authentication
return new Response("Login Successful!");
} catch (error) {
console.error(error);
return new Response("Authentication failed", { status: 403 });
}
},
},
"/": {
GET: () => {
// HTML content for the login page
const htmlContent = `<html><body><h2>Login with Google</h2><button onclick="window.location.href='/login'">Login</button></body></html>`;
return new Response(htmlContent, {
headers: { "Content-Type": "text/html" },
});
},
},
} satisfies Routes;

const server = serverFactory({
routes,
});

server.start(3000);
81 changes: 81 additions & 0 deletions auth/example/setup-oauth-providers.md
Original file line number Diff line number Diff line change
@@ -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.

6 changes: 5 additions & 1 deletion auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,9 @@ export {
createSecurityToken,
createToken,
getTokenExpireEpoch,
verifyToken,
verifyToken
} from "./security-token";

export { oAuthFactory } from "./oauth";

export { initGoogleOAuth, oAuthProviders } from "./oauth-providers";
33 changes: 33 additions & 0 deletions auth/oauth-providers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { OAuthConfig, OAuthProviderFn } from "./oauth-types";

export type ProvidersConfigRecord = Record<
string,
Omit<OAuthConfig, "clientId" | "clientSecret">
>;

export const oAuthProviders = {
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 ProvidersConfigRecord;

export const initGoogleOAuth: OAuthProviderFn = (
{ clientId, clientSecret },
options
) => {
const redirectUrl = options?.redirectUrl;
return {
...oAuthProviders.google,
redirectUri: redirectUrl ? redirectUrl : oAuthProviders.google.redirectUri,
clientId,
clientSecret,
};
};
36 changes: 36 additions & 0 deletions auth/oauth-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
export type OAuthHelpers = {
getAuthorizationUrl(config: OAuthConfig): string;
getToken(code: string, config: OAuthConfig): Promise<OAuthToken>; // 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
};

export type OAuthProviderOptions = {
redirectUrl: string;
};

export type OAuthProviderCreds = Pick<OAuthConfig, "clientId" | "clientSecret">;
export type OAuthProviderFn = (
config: OAuthProviderCreds,
options?: OAuthProviderOptions
) => OAuthConfig;
export type OAuthProviderInitializer = (config: OAuthConfig) => OAuthHelpers;
Loading