Skip to content

Commit

Permalink
Merge pull request #46 from auth0-lab/mfa_enroll
Browse files Browse the repository at this point in the history
replace `user.app_metadata.requires_mfa` with `/authorize?screen_hint=mfa`
  • Loading branch information
dschenkelman authored Oct 8, 2024
2 parents c2006fd + a17fea0 commit 44ed03b
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 133 deletions.
24 changes: 6 additions & 18 deletions app/api/auth/[auth0]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,7 @@ import { NextApiRequest, NextApiResponse } from "next";

import { handle3rdPartyParams } from "@/sdk/auth0/3rd-party-apis";
import { linkUser } from "@/sdk/auth0/mgmt";
import {
getSession,
handleAuth,
handleCallback,
handleLogin,
LoginOptions,
} from "@auth0/nextjs-auth0";
import { getSession, handleAuth, handleCallback, handleLogin, LoginOptions } from "@auth0/nextjs-auth0";

export const GET = handleAuth({
login: async (req: NextApiRequest, res: NextApiResponse) => {
Expand All @@ -18,8 +12,10 @@ export const GET = handleAuth({
const url = new URL(req.url!);
const thirdPartyApi = url.searchParams.get("3rdPartyApi") || "";
const linkWith = url.searchParams.get("linkWith");
const screenHint = url.searchParams.get("screenHint");

const authorizationParams = {
...(screenHint && { screen_hint: screenHint }),
...(thirdPartyApi && (await handle3rdPartyParams(thirdPartyApi))),
...(linkWith && {
connection: linkWith,
Expand All @@ -32,21 +28,15 @@ export const GET = handleAuth({
async getLoginState(_req: any, options: LoginOptions) {
if (linkWith) {
// store user id to be used during linking account from next login callback
return (
user && { primaryUserId: user.sub, secondaryProvider: linkWith }
);
return user && { primaryUserId: user.sub, secondaryProvider: linkWith };
}

return {};
},
});
},
callback: handleCallback({
afterCallback: async (
_req: any,
session: any,
state: { [key: string]: any }
) => {
afterCallback: async (_req: any, session: any, state: { [key: string]: any }) => {
const { primaryUserId, secondaryProvider } = state;
const { user } = session;

Expand All @@ -60,9 +50,7 @@ export const GET = handleAuth({
});

// force a silent login to get a fresh session for the updated user profile
state.returnTo = `/api/auth/login?returnTo=${encodeURI(
state.returnTo
)}`;
state.returnTo = `/api/auth/login?returnTo=${encodeURI(state.returnTo)}`;
}

return session;
Expand Down
89 changes: 24 additions & 65 deletions llm/components/conditional-purchase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,18 @@ import { CancelRedIcon, CheckGreenIcon, TaskIcon } from "@/components/icons";
import Loader from "@/components/loader";
import { ConditionalPurchase as ConditionalPurchaseType } from "@/lib/db/conditional-purchases";
import { getConditionalPurchaseById } from "@/llm/actions/conditional-purchases";
import {
isGuardianEnrolled,
requireGuardianEnrollment,
} from "@/sdk/auth0/mgmt";
import { isGuardianEnrolled } from "@/sdk/auth0/mgmt";

import WarningWrapper from "./warning-wrapper";

export function ConditionalPurchase({
id,
isMFAEnrolled,
}: {
id: string;
isMFAEnrolled: boolean;
}) {
export function ConditionalPurchase({ id, isMFAEnrolled }: { id: string; isMFAEnrolled: boolean }) {
const [isWorking, setIsWorking] = useState(true);
const [simulating, setSimulating] = useState(false);
const [isEnrolled, setIsEnrolled] = useState(isMFAEnrolled);
const [conditionalPurchase, setConditionalPurchase] =
useState<ConditionalPurchaseType | null>(null);
const [conditionalPurchase, setConditionalPurchase] = useState<ConditionalPurchaseType | null>(null);

const checkGuardianEnrollment = useCallback(async () => {
const isEnrolled = await isGuardianEnrolled();

if (!isEnrolled) {
await requireGuardianEnrollment();
}

setIsEnrolled(isEnrolled);
}, []);

Expand Down Expand Up @@ -68,18 +53,15 @@ export function ConditionalPurchase({
return (
<div className="border border-gray-300 rounded-xl p-6 flex items-center w-full justify-between">
<div className="flex flex-col gap-2">
<h2 className="text-base leading-6 font-semibold">
Action Required: Setup Async Authentication
</h2>
<h2 className="text-base leading-6 font-semibold">Action Required: Setup Async Authentication</h2>
<p className="text-sm leading-5 font-light text-gray-500">
Please enroll to Auth0 Guardian so we can notify you when the
condition is met. This is necessary to secure your future
transaction.
Please enroll to Auth0 Guardian so we can notify you when the condition is met. This is necessary to secure
your future transaction.
</p>
</div>
<div>
<a
href={`/api/auth/login?returnTo=${window.location.pathname}`}
href={`/api/auth/login?returnTo=${window.location.pathname}&screenHint=mfa`}
className="bg-gray-200 text-black whitespace-nowrap rounded-md text-sm font-normal transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 hover:bg-primary/90 hover:text-white py-2 px-4"
>
Enroll
Expand Down Expand Up @@ -122,10 +104,7 @@ export function ConditionalPurchase({
{!simulating && (
<div className="text-slate-500 text-sm leading-6">
To simulate the agent executing the task, please click{" "}
<button
className="border-b border-slate-500 leading-none"
onClick={simulateExecution}
>
<button className="border-b border-slate-500 leading-none" onClick={simulateExecution}>
here
</button>
.
Expand All @@ -142,22 +121,16 @@ export function ConditionalPurchase({
<div className="p-5 rounded-2xl bg-zinc-950 text-white">
<div className="flex flex-row justify-between">
<div className="flex flex-col gap-2">
<div className="text-base font-semibold text-white">
Buy {conditionalPurchase.symbol}
</div>
<div className="text-base font-semibold text-white">Buy {conditionalPurchase.symbol}</div>
<div className="text-sm text text-white/40 leading-6">
Created{" "}
{formatRelative(conditionalPurchase.created_at, new Date())}
Created {formatRelative(conditionalPurchase.created_at, new Date())}
</div>
</div>
<div className="flex flex-col gap-2">
<div className="text-base font-semibold text-green-400 text-right">
If {conditionalPurchase.metric} {conditionalPurchase.operator}{" "}
{conditionalPurchase.threshold}
</div>
<div className="text-sm text text-white/40 leading-6 uppercase">
Condition
If {conditionalPurchase.metric} {conditionalPurchase.operator} {conditionalPurchase.threshold}
</div>
<div className="text-sm text text-white/40 leading-6 uppercase">Condition</div>
</div>
</div>
<div className="border-t border-white/20 mt-5 pt-5">
Expand All @@ -166,10 +139,8 @@ export function ConditionalPurchase({
<TaskIcon />
</div>
<div className="text-white font-light">
Task: Buy {conditionalPurchase.quantity} $
{conditionalPurchase.symbol} shares if{" "}
{conditionalPurchase.metric} {conditionalPurchase.operator}{" "}
{conditionalPurchase.threshold}
Task: Buy {conditionalPurchase.quantity} ${conditionalPurchase.symbol} shares if{" "}
{conditionalPurchase.metric} {conditionalPurchase.operator} {conditionalPurchase.threshold}
</div>
</div>
</div>
Expand All @@ -183,22 +154,17 @@ export function ConditionalPurchase({
<div className="flex flex-row justify-between">
<div className="flex flex-col gap-2">
<div className="text-base font-semibold text-white">
Buy {conditionalPurchase.quantity} shares of{" "}
{conditionalPurchase.symbol}
Buy {conditionalPurchase.quantity} shares of {conditionalPurchase.symbol}
</div>
<div className="text-sm text text-white/40 leading-6">
Created{" "}
{formatRelative(conditionalPurchase.created_at, new Date())}
Created {formatRelative(conditionalPurchase.created_at, new Date())}
</div>
</div>
<div className="flex flex-col gap-2">
<div className="text-base font-semibold text-green-400">
If {conditionalPurchase.metric} {conditionalPurchase.operator}{" "}
{conditionalPurchase.threshold}
</div>
<div className="text-sm text text-white/40 leading-6 uppercase">
Condition
If {conditionalPurchase.metric} {conditionalPurchase.operator} {conditionalPurchase.threshold}
</div>
<div className="text-sm text text-white/40 leading-6 uppercase">Condition</div>
</div>
</div>
<div className="border-t border-white/20 mt-5 pt-5">
Expand All @@ -207,8 +173,7 @@ export function ConditionalPurchase({
<CancelRedIcon />
</div>
<div className="text-white font-light">
The purchase was{" "}
<strong className="text-red-400">CANCELED</strong> on{" "}
The purchase was <strong className="text-red-400">CANCELED</strong> on{" "}
{format(conditionalPurchase.updated_at, "PPPP p")}.
</div>
</div>
Expand All @@ -223,22 +188,17 @@ export function ConditionalPurchase({
<div className="flex flex-row justify-between">
<div className="flex flex-col gap-2">
<div className="text-base font-semibold text-white">
Buy {conditionalPurchase.quantity} shares of{" "}
{conditionalPurchase.symbol}
Buy {conditionalPurchase.quantity} shares of {conditionalPurchase.symbol}
</div>
<div className="text-sm text text-white/40 leading-6">
Created{" "}
{formatRelative(conditionalPurchase.created_at, new Date())}
Created {formatRelative(conditionalPurchase.created_at, new Date())}
</div>
</div>
<div className="flex flex-col gap-2">
<div className="text-base font-semibold text-green-400">
If {conditionalPurchase.metric} {conditionalPurchase.operator}{" "}
{conditionalPurchase.threshold}
</div>
<div className="text-sm text text-white/40 leading-6 uppercase">
Condition
If {conditionalPurchase.metric} {conditionalPurchase.operator} {conditionalPurchase.threshold}
</div>
<div className="text-sm text text-white/40 leading-6 uppercase">Condition</div>
</div>
</div>
<div className="border-t border-white/20 mt-5 pt-5">
Expand All @@ -247,8 +207,7 @@ export function ConditionalPurchase({
<CheckGreenIcon />
</div>
<div className="text-white font-light">
The purchase was{" "}
<strong className="text-green-400">COMPLETED</strong> on{" "}
The purchase was <strong className="text-green-400">COMPLETED</strong> on{" "}
{format(conditionalPurchase.updated_at, "PPPP p")}.
</div>
</div>
Expand Down
36 changes: 6 additions & 30 deletions llm/tools/add-conditional-purchase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,7 @@ import { defineTool } from "@/llm/ai-helpers";
import { ConditionalPurchase } from "@/llm/components/conditional-purchase";
import * as serialization from "@/llm/components/serialization";
import { getHistory } from "@/llm/utils";
import {
isGuardianEnrolled,
requireGuardianEnrollment,
} from "@/sdk/auth0/mgmt";
import { isGuardianEnrolled } from "@/sdk/auth0/mgmt";
import { getUser, withFGA } from "@/sdk/fga";
import { withCheckPermission } from "@/sdk/fga/vercel-ai/with-check-permission";

Expand All @@ -36,23 +33,11 @@ export default defineTool("add_conditional_purchase", () => {
2. 'Buy 10 shares of ZEKO but only when its P/E > 0' would map to the same inputs.
3. 'Buy 100 shares of Tesla when its price is less than or equal to $1000' maps to {symbol: 'TSLA', quantity: 100, metric: 'price', operator: '<=', threshold: 1000}.`,
parameters: z.object({
symbol: z
.string()
.describe(
"The name or ticker symbol of the stock (e.g., 'Zeko Technologies' or ZEKO)."
),
symbol: z.string().describe("The name or ticker symbol of the stock (e.g., 'Zeko Technologies' or ZEKO)."),
quantity: z.number().describe("Number of shares to buy."),
metric: z
.enum(["P/E", "EPS", "P/B", "D/E", "ROE", "RSI", "price"])
.describe("The financial metric to monitor."),
operator: z
.enum(["=", "<", "<=", ">", ">="])
.describe("The comparison operator to evaluate the condition."),
threshold: z
.number()
.describe(
"The threshold value of the financial variable that triggers the buy action."
),
metric: z.enum(["P/E", "EPS", "P/B", "D/E", "ROE", "RSI", "price"]).describe("The financial metric to monitor."),
operator: z.enum(["=", "<", "<=", ">", ">="]).describe("The comparison operator to evaluate the condition."),
threshold: z.number().describe("The threshold value of the financial variable that triggers the buy action."),
}),
generate: withCheckPermission<ToolParams>(
{
Expand All @@ -70,10 +55,6 @@ export default defineTool("add_conditional_purchase", () => {
const hs = headers();
const isEnrolled = await isGuardianEnrolled();

if (!isEnrolled) {
await requireGuardianEnrollment();
}

// Store the conditional purchase in Market0 db
let conditionalPurchase = await conditionalPurchases.create({
user_id: user.sub,
Expand All @@ -93,12 +74,7 @@ export default defineTool("add_conditional_purchase", () => {
params: { id: conditionalPurchase.id },
});

return (
<ConditionalPurchase
id={conditionalPurchase.id}
isMFAEnrolled={isEnrolled}
/>
);
return <ConditionalPurchase id={conditionalPurchase.id} isMFAEnrolled={isEnrolled} />;
}
),
};
Expand Down
27 changes: 7 additions & 20 deletions sdk/auth0/mgmt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,6 @@ export async function isGuardianEnrolled() {
}
}

export async function requireGuardianEnrollment() {
try {
const session = await getSession();
const user_id = session?.user.sub;

const user = await auth0.users.get({ id: user_id });
const app_metadata = user.data.app_metadata || {};

app_metadata.requires_mfa = true;

await auth0.users.update({ id: user_id }, { app_metadata });
} catch (error) {
console.error(error);
}
}

export async function getUser(userId: string) {
return auth0.users.get({ id: userId });
}
Expand Down Expand Up @@ -80,8 +64,11 @@ export async function updateUser(
userId: string,
{ givenName, familyName }: { givenName?: string; familyName?: string }
) {
return auth0.users.update({ id: userId }, {
given_name: givenName ?? undefined,
family_name: familyName ?? undefined,
});
return auth0.users.update(
{ id: userId },
{
given_name: givenName ?? undefined,
family_name: familyName ?? undefined,
}
);
}

0 comments on commit 44ed03b

Please sign in to comment.