Skip to content
This repository has been archived by the owner on Sep 22, 2024. It is now read-only.

Commit

Permalink
feat(plugins/auth): add assertAuthorization for simple authorizatio…
Browse files Browse the repository at this point in the history
…n based on user, req, and ctx
  • Loading branch information
miguelrk committed May 21, 2024
1 parent 7dcd8c3 commit 0861d1a
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 10 deletions.
34 changes: 31 additions & 3 deletions lib/plugins/auth/middlewares/mod.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import type { FreshContext } from "$fresh/server.ts";
import { AuthState } from "netzo/plugins/auth/plugin.ts";
import { getSessionId } from "../../../deps/deno_kv_oauth/mod.ts";
import type { NetzoState } from "../../../mod.ts";
import { AuthConfig, AuthState } from "../plugin.ts";
import { createDatastoreAuth } from "../utils/adapters/datastore.ts";
import { getFunctionsByProvider } from "../utils/providers/mod.ts";

type NetzoStateWithAuth = NetzoState & {
export type NetzoStateWithAuth = NetzoState & {
auth: AuthState;
};

Expand Down Expand Up @@ -54,7 +55,7 @@ export async function setSessionState(
return await ctx.next(); // C) authenticated
}

export async function assertUserIsWorkspaceUserOfWorkspaceOfApiKeyIfProviderIsNetzo(
export async function assertUserIsMemberOfWorkspaceOfApiKeyIfProviderIsNetzo(
req: Request,
ctx: FreshContext<NetzoStateWithAuth>,
) {
Expand Down Expand Up @@ -90,6 +91,33 @@ export async function assertUserIsWorkspaceUserOfWorkspaceOfApiKeyIfProviderIsNe
return await ctx.next();
}

export function createAssertUserIsAuthorized(config: AuthConfig) {
return async (req: Request, ctx: FreshContext<NetzoStateWithAuth>) => {
if (skip(req, ctx)) return await ctx.next();

if (ctx.url.pathname.startsWith("/auth")) return await ctx.next(); // skip auth route

const { sessionUser } = ctx.state.auth ?? {};

if (sessionUser) {
try {
await config?.assertAuthorization?.(sessionUser, req, ctx);
ctx.url.searchParams.delete("error");
} catch (e: Error | unknown) {
const [_, __, signOut] = getFunctionsByProvider(sessionUser.provider);
await signOut(req);
const { message = "You are not authorized to sign in." } = e as Error;
return new Response("", {
status: 307,
headers: { Location: `/auth?error=${message}` },
}); // redirect to relative path
}
}

return await ctx.next();
};
}

export async function setRequestState(
req: Request,
ctx: FreshContext<NetzoStateWithAuth>,
Expand Down
23 changes: 19 additions & 4 deletions lib/plugins/auth/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import type { Plugin, PluginRoute } from "$fresh/server.ts";
import type { FreshContext, Plugin, PluginRoute } from "$fresh/server.ts";
import type { OAuth2ClientConfig } from "../../deps/oauth2_client/src/oauth2_client.ts";
import type { NetzoState } from "../../mod.ts";
import {
assertUserIsWorkspaceUserOfWorkspaceOfApiKeyIfProviderIsNetzo,
assertUserIsMemberOfWorkspaceOfApiKeyIfProviderIsNetzo,
createAssertUserIsAuthorized,
ensureSignedIn,
NetzoStateWithAuth,
setAuthState,
setRequestState,
setSessionState,
Expand Down Expand Up @@ -38,6 +40,15 @@ export type AuthConfig = {
auth0?: OAuth2ClientConfig;
okta?: OAuth2ClientConfig;
};
/** A function to check if a user is authorized to sign in. The function should
* throw an Error with an optional error message if not authorized.
* @example throw new Error("User is not authorized to sign in.");
*/
assertAuthorization?: (
user: AuthUser,
req: Request,
ctx: FreshContext<NetzoStateWithAuth>,
) => Error | unknown;
};

export type AuthState = Auth & {
Expand Down Expand Up @@ -85,6 +96,7 @@ export const auth = (config: AuthConfig): Plugin<NetzoState> => {
config.description ??= "Sign in to access the app";
config.caption ??= ""; // e.g. 'By signing in you agree to the <a href="/" target="_blank">Terms of Service</a>';
config.providers ??= {};
config.assertAuthorization ??= () => true;

const authRoutes: PluginRoute[] = [
{ path: "/auth", component: createAuth(config) },
Expand All @@ -109,10 +121,13 @@ export const auth = (config: AuthConfig): Plugin<NetzoState> => {
{
path: "/",
middleware: {
handler:
assertUserIsWorkspaceUserOfWorkspaceOfApiKeyIfProviderIsNetzo,
handler: assertUserIsMemberOfWorkspaceOfApiKeyIfProviderIsNetzo,
},
},
{
path: "/",
middleware: { handler: createAssertUserIsAuthorized(config) },
},
{
path: "/",
middleware: { handler: setRequestState },
Expand Down
4 changes: 2 additions & 2 deletions lib/plugins/auth/utils/providers/netzo.handle_callback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
import { getCookies, setCookie } from "../../../../deps/deno_kv_oauth/deps.ts";
import {
COOKIE_BASE,
OAUTH_COOKIE_NAME,
SITE_COOKIE_NAME,
getCookieName,
isHttps,
OAUTH_COOKIE_NAME,
redirect,
SITE_COOKIE_NAME,
} from "../../../../deps/deno_kv_oauth/lib/_http.ts";
import { getAndDeleteOAuthSession } from "../../../../deps/deno_kv_oauth/lib/_kv.ts";
import { handleCallback } from "../../../../deps/deno_kv_oauth/mod.ts";
Expand Down
2 changes: 1 addition & 1 deletion lib/plugins/auth/utils/providers/netzo.sign_in.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import {
} from "../../../../deps/deno_kv_oauth/deps.ts";
import {
COOKIE_BASE,
OAUTH_COOKIE_NAME,
getCookieName,
getSuccessUrl,
isHttps,
OAUTH_COOKIE_NAME,
redirect,
} from "../../../../deps/deno_kv_oauth/lib/_http.ts";
import { setOAuthSession } from "../../../../deps/deno_kv_oauth/lib/_kv.ts";
Expand Down

0 comments on commit 0861d1a

Please sign in to comment.