Skip to content

Commit

Permalink
auto: format code
Browse files Browse the repository at this point in the history
  • Loading branch information
thdxr committed Dec 11, 2024
1 parent 02d3cc2 commit 9f3f9ed
Show file tree
Hide file tree
Showing 18 changed files with 139 additions and 118 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
# Trigger the workflow every time you push to the `main` branch
# Using a different branch name? Replace `main` with your branch’s name
push:
branches: [ master ]
branches: [master]
# Allows you to run this workflow manually from the Actions tab on GitHub.
workflow_dispatch:

Expand All @@ -23,7 +23,7 @@ jobs:
- name: Install, build, and upload your site
uses: withastro/action@v3
with:
path: www
path: www

deploy:
needs: build
Expand Down
10 changes: 4 additions & 6 deletions .github/workflows/format.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,26 @@ name: format

on:
push:
branches: [ master ]
branches: [master]
pull_request:
workflow_dispatch:

jobs:
format:
runs-on: ubuntu-latest

permissions:
contents: write
pull-requests: write

steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
fetch-depth: 0

- uses: oven-sh/setup-bun@v2
- run: bunx prettier --write "**/*.{js,jsx,ts,tsx,json,md,yaml,yml}"

- run: git diff --exit-code || echo "has_changes=true" >> $GITHUB_OUTPUT

- if: steps.git-check.outputs.has_changes == 'true'
run: |
git config --local user.email "github-actions[bot]@users.noreply.github.com"
Expand Down
1 change: 0 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,3 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

45 changes: 24 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ It adheres mostly to OAuth 2.0 specifications - which means anything that can sp

Because it follows these specifications it can even be used to issue credentials for third party applications - allowing you to implement "login with myapp" flows.

OpenAuth very intentionally does not attempt to solve user management. We've found that this is a very difficult problem given the wide range of databases and drivers that are used in the JS ecosystem. Additionally it's quite hard to build data abstractions that work for every use case. Instead, once a user has identified themselves OpenAuth will invoke a callback where you can implement your own user lookup/creation logic.
OpenAuth very intentionally does not attempt to solve user management. We've found that this is a very difficult problem given the wide range of databases and drivers that are used in the JS ecosystem. Additionally it's quite hard to build data abstractions that work for every use case. Instead, once a user has identified themselves OpenAuth will invoke a callback where you can implement your own user lookup/creation logic.

While OpenAuth tries to be mostly stateless, it does need to store a minimal amount of data (refresh tokens, password hashes, etc). However this has been reduced to a simple KV store with various implementations for zero overhead systems like Cloudflare KV and DynamoDB. You should never need to directly access any data that is stored in there.

Expand All @@ -59,7 +59,7 @@ We'll show how to deploy the auth server and then a sample app that uses it.
Start by importing the `authorizer` function from the `@openauthjs/openauth` package.

```ts
import { authorizer } from "@openauthjs/openauth";
import { authorizer } from "@openauthjs/openauth"
```

OpenAuth is built on top of [Hono](https://github.com/honojs/hono) which is a minimal web framework that can run anywhere. The `authorizer` function creates a Hono app with all of the auth server implemented that you can then deploy to AWS Lambda, Cloudflare Workers, or in a container running under Node.js or Bun.
Expand Down Expand Up @@ -130,15 +130,15 @@ const app = authorizer({
Next up is the `subjects` field. Subjects are what the access token generated at the end of the auth flow will map to. Under the hood, the access token is a JWT that contains this data. You will likely just have a single subject to start but you can define additional ones for different types of users.

```ts
import { object, string } from "valibot";
import { object, string } from "valibot"

const subjects = createSubjects({
user: object({
userID: string(),
// may want to add workspaceID here if doing a multi-tenant app
workspaceID: string(),
}),
});
})
```

Note we are using [valibot](https://github.com/Valibot/valibot) to define the shape of the subject so it can be validated properly. You can use any validation library that is following the [standard-schema specification](https://github.com/standard-schema/standard-schema) - the next version of Zod will support this.
Expand Down Expand Up @@ -207,11 +207,10 @@ import { handle } from "hono/aws-lambda"
export const handler = handle(app)

// Node.js
import { serve } from '@hono/node-server'
import { serve } from "@hono/node-server"
serve(app)
```


You now have a centralized auth server. Test it out by visiting `/.well-known/oauth-authorization-server` - you can see a live example [here](https://auth.terminal.shop/.well-known/oauth-authorization-server).

### Auth client
Expand All @@ -222,14 +221,15 @@ Since this is a standard OAuth server you can use any libraries for OAuth and it
import { createClient } from "@openauthjs/openauth/client"

const client = createClient("my-client", {
issuer: "https://auth.myserver.com" // this is the url for your auth server
issuer: "https://auth.myserver.com", // this is the url for your auth server
})
```

#### SSR Sites

If your frontend has a server component you can use the code flow. Redirect the user here

``` ts
```ts
const redirect = await client.authorize(
<client-id>,
<redirect-uri>,
Expand All @@ -247,12 +247,15 @@ console.log(tokens.access, tokens.refresh)
You likely want to store both the access token and refresh token in an HTTP only cookie so they are sent up with future requests. Then you can use the `client` to verify the tokens.

```ts
const verified = await client.verify(
subjects,
cookies.get("access_token")!,
{ refresh: cookies.get("refresh_token") || undefined },
);
console.log(verified.subject.type, verified.subject.properties, verified.refresh, verified.access);
const verified = await client.verify(subjects, cookies.get("access_token")!, {
refresh: cookies.get("refresh_token") || undefined,
})
console.log(
verified.subject.type,
verified.subject.properties,
verified.refresh,
verified.access,
)
```

Passing in the refresh token is optional but if you do, this function will automatically refresh the access token if it has expired. It will return a new access token and refresh token which you should set back into the cookies.
Expand All @@ -270,27 +273,27 @@ location.href = redirect;
When the auth flow is complete the user's browser will be redirected to the `redirect_uri` with a `code` query parameter. You can then exchange the code for access/refresh tokens.

```ts
const verifier = localStorage.getItem("verifier");
const tokens = await client.exchange(query.get("code"), redirect_uri, verifier);
localStorage.setItem("access_token", tokens.access);
localStorage.setItem("refresh_token", tokens.refresh);
const verifier = localStorage.getItem("verifier")
const tokens = await client.exchange(query.get("code"), redirect_uri, verifier)
localStorage.setItem("access_token", tokens.access)
localStorage.setItem("refresh_token", tokens.refresh)
```

Then when you make requests to your API you can include the access token in the `Authorization` header.

```ts
const accessToken = localStorage.getItem("access_token");
const accessToken = localStorage.getItem("access_token")
fetch("https://auth.example.com/api/user", {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
})
```

And then you can verify the access token on the server.

```ts
const verified = await client.verify(subjects, accessToken);
const verified = await client.verify(subjects, accessToken)
console.log(verified.subject)
```

Expand Down
1 change: 1 addition & 0 deletions examples/client/astro/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# OpenAuth Astro Client

The files to note are

- `src/auth.ts` - creates the client that is used to interact with the auth server
- `src/middleware.ts` - middleware that runs to verify access tokens, refresh them if out of date, and redirect the user to the auth server if they are not logged in
- `src/pages/callback.ts` - the callback endpoint that receives the auth code and exchanges it for an access/refresh token
1 change: 1 addition & 0 deletions examples/client/react-router/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# OpenAuth React Router Client

The files to note are

- `app/auth.ts` - creates the client that is used to interact with the auth server, along with code that runs to verify access tokens, refresh them if out of date, and redirect the user to the auth server if they are not logged in
- `app/routes/auth/callback.ts` - the callback endpoint that receives the auth code and exchanges it for an access/refresh token
24 changes: 12 additions & 12 deletions examples/client/react-router/app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import {
Outlet,
Scripts,
ScrollRestoration,
} from "react-router";
import type { Route } from "./+types/root";
} from "react-router"
import type { Route } from "./+types/root"

export function Layout({ children }: { children: React.ReactNode }) {
return (
Expand All @@ -23,27 +23,27 @@ export function Layout({ children }: { children: React.ReactNode }) {
<Scripts />
</body>
</html>
);
)
}

export default function App() {
return <Outlet />;
return <Outlet />
}

export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
let message = "Oops!";
let details = "An unexpected error occurred.";
let stack: string | undefined;
let message = "Oops!"
let details = "An unexpected error occurred."
let stack: string | undefined

if (isRouteErrorResponse(error)) {
message = error.status === 404 ? "404" : "Error";
message = error.status === 404 ? "404" : "Error"
details =
error.status === 404
? "The requested page could not be found."
: error.statusText || details;
: error.statusText || details
} else if (import.meta.env.DEV && error && error instanceof Error) {
details = error.message;
stack = error.stack;
details = error.message
stack = error.stack
}

return (
Expand All @@ -56,5 +56,5 @@ export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
</pre>
)}
</main>
);
)
}
6 changes: 3 additions & 3 deletions examples/client/react-router/app/routes/home.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { tryAuth } from "~/auth";
import type { Route } from "./+types/home";
import { data, Form, Link } from "react-router";
import { tryAuth } from "~/auth"
import type { Route } from "./+types/home"
import { data, Form, Link } from "react-router"

export async function loader({ request }: Route.LoaderArgs) {
const auth = await tryAuth(request)
Expand Down
2 changes: 1 addition & 1 deletion examples/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
"moduleResolution": "Bundler",
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx"
},
}
}
34 changes: 17 additions & 17 deletions packages/openauth/src/ui/base.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
import { PropsWithChildren } from "hono/jsx";
import css from "./ui.css" assert { type: "text" };
import { getTheme } from "./theme.js";
import { PropsWithChildren } from "hono/jsx"
import css from "./ui.css" assert { type: "text" }
import { getTheme } from "./theme.js"

export function Layout(
props: PropsWithChildren<{
size?: "small";
size?: "small"
}>,
) {
const theme = getTheme();
const theme = getTheme()
function get(key: "primary" | "background" | "logo", mode: "light" | "dark") {
if (!theme) return;
if (!theme[key]) return;
if (typeof theme[key] === "string") return theme[key];
if (!theme) return
if (!theme[key]) return
if (typeof theme[key] === "string") return theme[key]

return theme[key][mode] as string | undefined;
return theme[key][mode] as string | undefined
}

const radius = (() => {
if (theme?.radius === "none") return "0";
if (theme?.radius === "sm") return "1";
if (theme?.radius === "md") return "1.25";
if (theme?.radius === "lg") return "1.5";
if (theme?.radius === "full") return "1000000000001";
return "1";
})();
if (theme?.radius === "none") return "0"
if (theme?.radius === "sm") return "1"
if (theme?.radius === "md") return "1.25"
if (theme?.radius === "lg") return "1.5"
if (theme?.radius === "full") return "1000000000001"
return "1"
})()

return (
<html
Expand Down Expand Up @@ -64,5 +64,5 @@ export function Layout(
</div>
</body>
</html>
);
)
}
22 changes: 11 additions & 11 deletions packages/openauth/src/ui/code.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/** @jsxImportSource hono/jsx */

import { CodeAdapterOptions } from "../adapter/code.js";
import { UnknownStateError } from "../error.js";
import { Layout } from "./base.js";
import { FormAlert } from "./form.js";
import { CodeAdapterOptions } from "../adapter/code.js"
import { UnknownStateError } from "../error.js"
import { Layout } from "./base.js"
import { FormAlert } from "./form.js"

export function CodeUI(props: {
sendCode: (claims: Record<string, string>, code: string) => Promise<void>;
sendCode: (claims: Record<string, string>, code: string) => Promise<void>
}) {
return {
sendCode: props.sendCode,
Expand Down Expand Up @@ -35,12 +35,12 @@ export function CodeUI(props: {
We&apos;ll send a pin code to your email
</p>
</Layout>
);
)
return new Response(jsx.toString(), {
headers: {
"Content-Type": "text/html",
},
});
})
}

if (state.type === "code") {
Expand Down Expand Up @@ -87,15 +87,15 @@ export function CodeUI(props: {
</div>
</form>
</Layout>
);
)
return new Response(jsx.toString(), {
headers: {
"Content-Type": "text/html",
},
});
})
}

throw new UnknownStateError();
throw new UnknownStateError()
},
} satisfies CodeAdapterOptions;
} satisfies CodeAdapterOptions
}
6 changes: 3 additions & 3 deletions packages/openauth/src/ui/form.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/** @jsxImportSource hono/jsx */

export function FormAlert(props: {
message?: string;
color?: "danger" | "success";
message?: string
color?: "danger" | "success"
}) {
return (
<div data-component="form-alert" data-color={props.color}>
Expand All @@ -23,5 +23,5 @@ export function FormAlert(props: {
</svg>
<span data-slot="message">{props.message}</span>
</div>
);
)
}
Loading

0 comments on commit 9f3f9ed

Please sign in to comment.