diff --git a/README.md b/README.md index a314ef2c0c..692ca98ec9 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ We offer a hosted version of Inbox Zero at [https://getinboxzero.com](https://ge ### Setup -[Here's a video](https://youtu.be/hVQENQ4WT2Y) on how to set up the project. It covers the same steps mentioned in this document. But goes into greater detail on setting up the external services. +[Here's a video](https://youtu.be/hVQENQ4WT2Y) on how to set up the project. It covers the same steps mentioned in this document. But goes into greater detail on setting up the external services. ### Requirements @@ -125,45 +125,100 @@ Go to [Google Cloud](https://console.cloud.google.com/). Create a new project if Create [new credentials](https://console.cloud.google.com/apis/credentials): -1. If the banner shows up, configure **consent screen** (if not, you can do this later) - 1. Click the banner, then Click `Get Started`. - 2. Choose a name for your app, and enter your email. - 3. In Audience, choose `External` - 4. Enter your contact information - 5. Agree to the User Data policy and then click `Create`. - 6. Return to APIs and Services using the left sidebar. -2. Create new [credentials](https://console.cloud.google.com/apis/credentials): - 1. Click the `+Create Credentials` button. Choose OAuth Client ID. - 2. In `Application Type`, Choose `Web application` - 3. Choose a name for your web client - 4. In Authorized JavaScript origins, add a URI and enter `http://localhost:3000` - 5. In `Authorized redirect URIs` enter `http://localhost:3000/api/auth/callback/google` - 6. Click `Create`. - 7. A popup will show up with the new credentials, including the Client ID and secret. -3. Update .env file: - 1. Copy the Client ID to `GOOGLE_CLIENT_ID` - 2. Copy the Client secret to `GOOGLE_CLIENT_SECRET` -4. Update [scopes](https://console.cloud.google.com/auth/scopes) - - 1. Go to `Data Access` in the left sidebar (or click link above) - 2. Click `Add or remove scopes` - 3. Copy paste the below into the `Manually add scopes` box: - - ```plaintext - https://www.googleapis.com/auth/userinfo.profile - https://www.googleapis.com/auth/userinfo.email - https://www.googleapis.com/auth/gmail.modify - https://www.googleapis.com/auth/gmail.settings.basic - https://www.googleapis.com/auth/contacts - ``` - - 4. Click `Update` - 5. Click `Save` in the Data Access page. - -5. Add yourself as a test user - 1. Go to [Audience](https://console.cloud.google.com/auth/audience) - 2. In the `Test users` section, click `+Add users` - 3. Enter your email and press `Save` +1. If the banner shows up, configure **consent screen** (if not, you can do this later) + + 1. Click the banner, then Click `Get Started`. + 2. Choose a name for your app, and enter your email. + 3. In Audience, choose `External` + 4. Enter your contact information + 5. Agree to the User Data policy and then click `Create`. + 6. Return to APIs and Services using the left sidebar. + +2. Create new [credentials](https://console.cloud.google.com/apis/credentials): + + 1. Click the `+Create Credentials` button. Choose OAuth Client ID. + 2. In `Application Type`, Choose `Web application` + 3. Choose a name for your web client + 4. In Authorized JavaScript origins, add a URI and enter `http://localhost:3000` + 5. In `Authorized redirect URIs` enter `http://localhost:3000/api/auth/callback/google` + 6. Click `Create`. + 7. A popup will show up with the new credentials, including the Client ID and secret. + +3. Update .env file: + + 1. Copy the Client ID to `GOOGLE_CLIENT_ID` + 2. Copy the Client secret to `GOOGLE_CLIENT_SECRET` + +4. Update [scopes](https://console.cloud.google.com/auth/scopes) + + 1. Go to `Data Access` in the left sidebar (or click link above) + 2. Click `Add or remove scopes` + 3. Copy paste the below into the `Manually add scopes` box: + + ```plaintext + https://www.googleapis.com/auth/userinfo.profile + https://www.googleapis.com/auth/userinfo.email + https://www.googleapis.com/auth/gmail.modify + https://www.googleapis.com/auth/gmail.settings.basic + https://www.googleapis.com/auth/contacts + ``` + + 4. Click `Update` + 5. Click `Save` in the Data Access page. + +5. Add yourself as a test user + + 1. Go to [Audience](https://console.cloud.google.com/auth/audience) + 2. In the `Test users` section, click `+Add users` + 3. Enter your email and press `Save` + +### Updating .env file with Azure credentials: + +- `MICROSOFT_CLIENT_ID` -- Google OAuth client ID. More info [here](https://authjs.dev/getting-started/providers/microsoft-entra-id) +- `MICROSOFT_CLIENT_SECRET` -- Google OAuth client secret. More info [here](https://authjs.dev/getting-started/providers/microsoft-entra-id) +- `MICROSOFT_ISSUER` -- Google OAuth client secret. More info [here](https://authjs.dev/getting-started/providers/microsoft-entra-id) + +Go to [Azure Portal](https://portal.azure.com/). Create a new application if necessary. + +Create the [application](https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/CreateApplicationBlade): + +1. Register an application in [Azure Portal](https://portal.azure.com/): + + 1. Go to **Azure Active Directory** > **App registrations** > **New registration**. + 2. Enter a name for your app. + 3. Set **Supported account types** as needed (e.g., "Accounts in any organizational directory and personal Microsoft accounts"). + 4. In **Redirect URI**, select "Web" and enter: `http://localhost:3000/api/auth/callback/microsoft-entra-id` + 5. Click **Register**. + +2. Configure authentication: + + 1. In your app registration, go to **Authentication**. + 2. Ensure the redirect URI `http://localhost:3000/api/auth/callback/microsoft-entra-id` is listed. + 3. (Optional) Enable **Access tokens** and **ID tokens**. + +3. Create a client secret: + + 1. Go to **Certificates & secrets** > **New client secret**. + 2. Add a description and set an expiry. + 3. Click **Add** and copy the generated value (you won't see it again). + +4. Update your `.env` file: + +- Set `MICROSOFT_CLIENT_ID` to the **Application (client) ID** from the app registration overview. +- Set `MICROSOFT_CLIENT_SECRET` to the value of the client secret you just created. +- Set `MICROSOFT_ISSUER` to your **Directory (tenant) ID** or use `https://login.microsoftonline.com/common/v2.0` for multi-tenant. + +5. Add API permissions: + 1. Go to **API permissions** > **Add a permission**. + 2. Choose **Microsoft Graph** > **Delegated permissions**. + 3. Add permissions such as `User.Read`, `Mail.ReadWrite`, `Mail.Send`, `offline_access`, and any others your app requires. + 4. Click **Add permissions**. + 5. (If needed) Click **Grant admin consent**. + 6. (Optional) Add yourself as a test user: + +- If your app is restricted, ensure your Microsoft account is added as a user in the Azure AD tenant. + +For more details, see the [Auth.js Microsoft Entra ID provider docs](https://authjs.dev/getting-started/providers/microsoft-entra-id). ### Updating .env file with LLM parameters diff --git a/apps/web/.env.example b/apps/web/.env.example index 13c1504234..f0429650db 100644 --- a/apps/web/.env.example +++ b/apps/web/.env.example @@ -10,6 +10,10 @@ GOOGLE_CLIENT_SECRET= GOOGLE_ENCRYPT_SECRET= # openssl rand -hex 32 GOOGLE_ENCRYPT_SALT= # openssl rand -hex 16 +MICROSOFT_CLIENT_ID= +MICROSOFT_CLIENT_SECRET= +MICROSOFT_TENANT_ID= + GOOGLE_PUBSUB_TOPIC_NAME="projects/abc/topics/xyz" GOOGLE_PUBSUB_VERIFICATION_TOKEN= # Generate a random secret here: https://generate-secret.vercel.app/32 diff --git a/apps/web/app/(app)/[emailAccountId]/assistant/page.tsx b/apps/web/app/(app)/[emailAccountId]/assistant/page.tsx index c6dead6851..9778e3a813 100644 --- a/apps/web/app/(app)/[emailAccountId]/assistant/page.tsx +++ b/apps/web/app/(app)/[emailAccountId]/assistant/page.tsx @@ -28,9 +28,10 @@ export default async function AssistantPage({ select: { id: true }, }); - if (!hasRule) { - redirect(prefixPath(emailAccountId, "/assistant?onboarding=true")); - } + // FIXME: redirected too many times + // if (!hasRule) { + // redirect(prefixPath(emailAccountId, "/assistant?onboarding=true")); + // } } return ( diff --git a/apps/web/app/(app)/[emailAccountId]/mail/page.tsx b/apps/web/app/(app)/[emailAccountId]/mail/page.tsx index 62aa6212a6..d0dda81a1b 100644 --- a/apps/web/app/(app)/[emailAccountId]/mail/page.tsx +++ b/apps/web/app/(app)/[emailAccountId]/mail/page.tsx @@ -7,43 +7,66 @@ import { List } from "@/components/email-list/EmailList"; import { LoadingContent } from "@/components/LoadingContent"; import type { ThreadsQuery } from "@/app/api/google/threads/validation"; import type { ThreadsResponse } from "@/app/api/google/threads/controller"; +import type { OutlookThreadsResponse } from "@/app/api/outlook/threads/controller"; import { refetchEmailListAtom } from "@/store/email"; import { BetaBanner } from "@/app/(app)/[emailAccountId]/mail/BetaBanner"; import { ClientOnly } from "@/components/ClientOnly"; import { PermissionsCheck } from "@/app/(app)/[emailAccountId]/PermissionsCheck"; +// You may get this from props, context, or user/account info +// For this example, let's assume it's a prop: export default function Mail(props: { - searchParams: Promise<{ type?: string; labelId?: string }>; + searchParams: Promise<{ + type?: string; + labelId?: string; + folderId?: string; + provider?: "gmail" | "outlook"; + }>; }) { const searchParams = use(props.searchParams); - const query: ThreadsQuery = {}; + const provider = searchParams.provider || "gmail"; // default to gmail if not set - // Handle different query params - if (searchParams.type === "label" && searchParams.labelId) { - query.labelId = searchParams.labelId; - } else if (searchParams.type) { - query.type = searchParams.type; + // Build the query object + const query: ThreadsQuery = {}; + if (provider === "gmail") { + if (searchParams.type === "label" && searchParams.labelId) { + query.labelId = searchParams.labelId; + } else if (searchParams.type) { + query.type = searchParams.type; + } + } else if (provider === "outlook") { + if (searchParams.type === "folder" && searchParams.folderId) { + query.folderId = searchParams.folderId; + } else if (searchParams.type) { + query.type = searchParams.type; + } } + // Build the correct endpoint + const endpoint = + provider === "gmail" ? "/api/google/threads" : "/api/outlook/threads"; + + // SWR key builder const getKey = ( pageIndex: number, - previousPageData: ThreadsResponse | null, + previousPageData: ThreadsResponse | OutlookThreadsResponse | null, ) => { if (previousPageData && !previousPageData.nextPageToken) return null; const queryParams = new URLSearchParams(query as Record); - // Append nextPageToken for subsequent pages if (pageIndex > 0 && previousPageData?.nextPageToken) { queryParams.set("nextPageToken", previousPageData.nextPageToken); } - return `/api/google/threads?${queryParams.toString()}`; + return `${endpoint}?${queryParams.toString()}`; }; - const { data, size, setSize, isLoading, error, mutate } = - useSWRInfinite(getKey, { - keepPreviousData: true, - dedupingInterval: 1_000, - revalidateOnFocus: false, - }); + // Use correct response type for SWR + const { data, size, setSize, isLoading, error, mutate } = useSWRInfinite< + ThreadsResponse | OutlookThreadsResponse + >(getKey, { + keepPreviousData: true, + dedupingInterval: 1_000, + revalidateOnFocus: false, + }); const allThreads = data ? data.flatMap((page) => page.threads) : []; const isLoadingMore = @@ -51,7 +74,6 @@ export default function Mail(props: { const showLoadMore = data ? !!data[data.length - 1]?.nextPageToken : false; // store `refetch` in the atom so we can refresh the list upon archive via command k - // TODO is this the best way to do this? const refetch = useCallback( (options?: { removedThreadIds?: string[] }) => { mutate( @@ -76,7 +98,6 @@ export default function Mail(props: { [mutate], ); - // Set up the refetch function in the atom store const setRefetchEmailList = useSetAtom(refetchEmailListAtom); useEffect(() => { setRefetchEmailList({ refetch }); @@ -88,7 +109,7 @@ export default function Mail(props: { return ( <> - + {provider !== "outlook" && } @@ -101,6 +122,7 @@ export default function Mail(props: { showLoadMore={showLoadMore} handleLoadMore={handleLoadMore} isLoadingMore={isLoadingMore} + provider={provider} /> )} diff --git a/apps/web/app/(landing)/login/LoginForm.tsx b/apps/web/app/(landing)/login/LoginForm.tsx index b32da98191..6f106bd07d 100644 --- a/apps/web/app/(landing)/login/LoginForm.tsx +++ b/apps/web/app/(landing)/login/LoginForm.tsx @@ -75,6 +75,59 @@ export function LoginForm() { + {/* TODO: Check the best way to filter for the waitlist users */} + + + + + + + Sign in + + + Inbox Zero{"'"}s use and transfer of information received from + Microsoft Entra ID will adhere to{" "} + + Microsoft Entra Privacy Policy + {" "} + and other applicable requirements. + +
+ +
+
+
+