Skip to content

Commit

Permalink
Use Auth0 as login provider (#4156)
Browse files Browse the repository at this point in the history
* Add auth0 sub in user model

* Use Auth0 for login

* ✨ 👕

* Fix typeguard

* Add GitHub temporary authorization callback URL

* Override AUTH0_BASE_URL in front-edge
  • Loading branch information
flvndvd authored Mar 5, 2024
1 parent feee1d1 commit c6defc0
Show file tree
Hide file tree
Showing 17 changed files with 310 additions and 332 deletions.
8 changes: 1 addition & 7 deletions front/components/sparkle/AppLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import Link from "next/link";
import type { NextRouter } from "next/router";
import { useRouter } from "next/router";
import Script from "next/script";
import { signOut } from "next-auth/react";
import React, { Fragment, useContext, useEffect, useState } from "react";

import { CONVERSATION_PARENT_SCROLL_DIV_ID } from "@app/components/assistant/conversation/lib";
Expand Down Expand Up @@ -102,12 +101,7 @@ function NavigationBar({
<DropdownMenu.Items origin="topRight">
<DropdownMenu.Item
label="Sign&nbsp;out"
onClick={() => {
void signOut({
callbackUrl: "/",
redirect: true,
});
}}
href="/api/auth/logout"
/>
</DropdownMenu.Items>
</DropdownMenu>
Expand Down
38 changes: 22 additions & 16 deletions front/lib/auth.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getSession as getAuth0Session } from "@auth0/nextjs-auth0";
import type {
RoleType,
UserType,
Expand All @@ -21,10 +22,10 @@ import type {
NextApiRequest,
NextApiResponse,
} from "next";
import type { Session } from "next-auth";
import { getServerSession } from "next-auth/next";

import { isDevelopment } from "@app/lib/development";
import type { SessionWithUser } from "@app/lib/iam/provider";
import { isValidSession } from "@app/lib/iam/provider";
import {
FeatureFlag,
Key,
Expand All @@ -39,7 +40,6 @@ import { FREE_TEST_PLAN_DATA } from "@app/lib/plans/free_plans";
import { isUpgraded } from "@app/lib/plans/plan_codes";
import { new_id } from "@app/lib/utils";
import logger from "@app/logger/logger";
import { authOptions } from "@app/pages/api/auth/[...nextauth]";

const {
DUST_DEVELOPMENT_WORKSPACE_ID,
Expand Down Expand Up @@ -86,13 +86,16 @@ export class Authenticator {

/**
* Get a an Authenticator for the target workspace associated with the authentified user from the
* NextAuth session.
* Auth0 session.
*
* @param session any NextAuth session
* @param session any Auth0 session
* @param wId string target workspace id
* @returns Promise<Authenticator>
*/
static async fromSession(session: any, wId: string): Promise<Authenticator> {
static async fromSession(
session: SessionWithUser | null,
wId: string
): Promise<Authenticator> {
const [workspace, user] = await Promise.all([
(async () => {
return Workspace.findOne({
Expand All @@ -107,8 +110,7 @@ export class Authenticator {
} else {
return User.findOne({
where: {
provider: session.provider.provider,
providerId: session.provider.id.toString(),
auth0Sub: session.user.sub,
},
});
}
Expand Down Expand Up @@ -157,15 +159,15 @@ export class Authenticator {

/**
* Get a an Authenticator for the target workspace and the authentified Super User user from the
* NextAuth session.
* Auth0 session.
* Super User will have `role` set to `admin` regardless of their actual role in the workspace.
*
* @param session any NextAuth session
* @param session any Auth0 session
* @param wId string target workspace id
* @returns Promise<Authenticator>
*/
static async fromSuperUserSession(
session: any,
session: SessionWithUser | null,
wId: string | null
): Promise<Authenticator> {
const [workspace, user] = await Promise.all([
Expand All @@ -185,8 +187,7 @@ export class Authenticator {
} else {
return User.findOne({
where: {
provider: session.provider.provider,
providerId: session.provider.id.toString(),
auth0Sub: session.user.sub,
},
});
}
Expand Down Expand Up @@ -439,16 +440,21 @@ export class Authenticator {
}

/**
* Retrieves the NextAuth session from the request/response.
* Retrieves the Auth0 session from the request/response.
* @param req NextApiRequest request object
* @param res NextApiResponse response object
* @returns Promise<any>
*/
export async function getSession(
req: NextApiRequest | GetServerSidePropsContext["req"],
res: NextApiResponse | GetServerSidePropsContext["res"]
): Promise<Session | null> {
return getServerSession(req, res, authOptions);
): Promise<SessionWithUser | null> {
const session = await getAuth0Session(req, res);
if (!session || !isValidSession(session)) {
return null;
}

return session;
}

/**
Expand Down
64 changes: 25 additions & 39 deletions front/lib/iam/provider.ts
Original file line number Diff line number Diff line change
@@ -1,56 +1,42 @@
import type { UserProviderType } from "@dust-tt/types";
import type { Session } from "@auth0/nextjs-auth0";

interface LegacyProvider {
provider: UserProviderType;
id: number | string;
}

interface LegacyExternalUser {
name: string;
// This maps to the Auth0 user.
export interface ExternalUser {
email: string;
image?: string;
username?: string;
email_verified?: boolean;
}
email_verified: boolean;
name: string;
nickname: string;
sub: string;

// Google-specific fields.
family_name?: string;
given_name?: string;

interface LegacySession {
provider: LegacyProvider;
user: LegacyExternalUser;
// Always optional.
picture?: string;
}

function isLegacyExternalUser(user: unknown): user is LegacyExternalUser {
function isExternalUser(user: Session["user"]): user is ExternalUser {
return (
typeof user === "object" &&
user !== null &&
"email" in user &&
"name" in user
"email_verified" in user &&
"name" in user &&
"nickname" in user &&
"sub" in user
);
}

function isLegacyProvider(provider: unknown): provider is LegacyProvider {
return (
typeof provider === "object" &&
provider !== null &&
"provider" in provider &&
"id" in provider
);
}

export function isLegacySession(session: unknown): session is LegacySession {
return (
typeof session === "object" &&
session !== null &&
"provider" in session &&
isLegacyProvider(session.provider) &&
"user" in session &&
isLegacyExternalUser(session.user)
);
function isAuth0Session(session: unknown): session is Session {
return typeof session === "object" && session !== null && "user" in session;
}

// We only expose generic types to ease phasing out.

export type Session = LegacySession;
export type SessionWithUser = Omit<Session, "user"> & { user: ExternalUser };

export function isValidSession(session: unknown): session is Session {
return isLegacySession(session);
export function isValidSession(
session: Session | null
): session is SessionWithUser {
return isAuth0Session(session) && isExternalUser(session.user);
}
20 changes: 9 additions & 11 deletions front/lib/iam/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { ParsedUrlQuery } from "querystring";
import { Op } from "sequelize";

import { getSession } from "@app/lib/auth";
import type { Session } from "@app/lib/iam/provider";
import type { SessionWithUser } from "@app/lib/iam/provider";
import { isValidSession } from "@app/lib/iam/provider";
import {
fetchUserFromSession,
Expand All @@ -17,19 +17,15 @@ import {
import { Membership, Workspace } from "@app/lib/models";
import { withGetServerSidePropsLogging } from "@app/logger/withlogging";

export function isGoogleSession(session: any) {
return session.provider.provider === "google";
}

/**
* Retrieves the user for a given session
* @param session any NextAuth session
* @param session any Auth0 session
* @returns Promise<UserType | null>
*/
export async function getUserFromSession(
session: any
session: SessionWithUser | null
): Promise<UserTypeWithWorkspaces | null> {
if (!session) {
if (!session || !isValidSession(session)) {
return null;
}

Expand Down Expand Up @@ -100,7 +96,7 @@ export type CustomGetServerSideProps<
RequireAuth extends boolean = true
> = (
context: GetServerSidePropsContext<Params, Preview>,
session: RequireAuth extends true ? Session : null
session: RequireAuth extends true ? SessionWithUser : null
) => Promise<GetServerSidePropsResult<Props>>;

export function makeGetServerSidePropsRequirementsWrapper<
Expand All @@ -123,12 +119,14 @@ export function makeGetServerSidePropsRequirementsWrapper<
redirect: {
permanent: false,
// TODO(2024-03-04 flav) Add support for `returnTo=`.
destination: "/",
destination: "/api/auth/login",
},
};
}

const userSession = session as RequireAuth extends true ? Session : null;
const userSession = session as RequireAuth extends true
? SessionWithUser
: null;

if (enableLogging) {
return withGetServerSidePropsLogging(getServerSideProps)(
Expand Down
Loading

0 comments on commit c6defc0

Please sign in to comment.