Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
102 commits
Select commit Hold shift + click to select a range
8de7e60
Add consent and account_select to Microsoft Entra
edulelis Jul 31, 2025
731a067
Merge pull request #613 from edulelis/staging-test-select-account-prompt
edulelis Jul 31, 2025
0d93f19
Simple consent test
edulelis Jul 31, 2025
19390fd
Merge pull request #614 from edulelis/staging-test-select-account-prompt
edulelis Jul 31, 2025
4bf10c0
Enable AuthJS debug
edulelis Jul 31, 2025
9b65228
Merge pull request #619 from edulelis/staging-test-select-account-prompt
edulelis Aug 1, 2025
fddf234
Set logger, update error page
edulelis Aug 1, 2025
523e85f
Remove unused import
edulelis Aug 1, 2025
a665d97
Merge pull request #620 from edulelis/staging-test-select-account-prompt
edulelis Aug 1, 2025
0b61cb2
Add temporary bypass
edulelis Aug 1, 2025
5132460
Merge pull request #621 from edulelis/staging-test-select-account-prompt
edulelis Aug 1, 2025
05814ed
Separate google/microsoft-entra-id logic on jwt
edulelis Aug 1, 2025
df93d75
Merge pull request #622 from edulelis/staging-test-select-account-prompt
edulelis Aug 1, 2025
fe105f9
Allow dangerous linking
edulelis Aug 1, 2025
7cb0e72
Merge pull request #623 from edulelis/staging-test-select-account-prompt
edulelis Aug 1, 2025
e2f87d8
Log user, account
edulelis Aug 1, 2025
33ccd37
Merge pull request #624 from edulelis/staging-test-select-account-prompt
edulelis Aug 1, 2025
7ab95b7
Update token expiry logic
edulelis Aug 1, 2025
7b5186a
Merge pull request #625 from edulelis/staging-test-select-account-prompt
edulelis Aug 1, 2025
cf47be1
Skip checks as test
edulelis Aug 1, 2025
b2a4d5a
Merge pull request #626 from edulelis/staging-test-select-account-prompt
edulelis Aug 1, 2025
7a5120b
Add missing authorization url
edulelis Aug 1, 2025
9d3cc73
Merge pull request #627 from edulelis/staging-test-select-account-prompt
edulelis Aug 1, 2025
0970d39
Copy google provider props
edulelis Aug 1, 2025
8968ef9
Merge pull request #628 from edulelis/staging-test-select-account-prompt
edulelis Aug 1, 2025
02e7c07
Test with reduced scope
edulelis Aug 1, 2025
1160cd2
Merge pull request #629 from edulelis/staging-test-select-account-prompt
edulelis Aug 1, 2025
a035c32
better-auth migration
edulelis Aug 4, 2025
d3cd238
Merge branch 'staging' of github.com:elie222/inbox-zero into migrate-…
edulelis Aug 4, 2025
e4c18f9
Merge branch 'main' into staging
elie222 Aug 4, 2025
11684ca
Merge branch 'staging' of github.com:elie222/inbox-zero into migrate-…
edulelis Aug 4, 2025
0dd2b85
Bugfixes. Update readme. Migration
edulelis Aug 4, 2025
6cc9e42
Simplify login try/catch
edulelis Aug 4, 2025
9b7656c
Merge pull request #632 from edulelis/migrate-to-better-auth
edulelis Aug 4, 2025
78f92ee
Update lockfile
edulelis Aug 5, 2025
a53fcbe
Merge pull request #633 from edulelis/migrate-to-better-auth
edulelis Aug 5, 2025
cdb4ea0
Fix expiration type
edulelis Aug 5, 2025
191185c
Merge pull request #634 from edulelis/migrate-to-better-auth
edulelis Aug 5, 2025
bdb31cc
Uncomment outlook scopes
edulelis Aug 5, 2025
44fbb80
Merge pull request #635 from edulelis/migrate-to-better-auth
edulelis Aug 5, 2025
2cd1b55
Reduce diff in schema
edulelis Aug 5, 2025
6ec1175
Merge pull request #636 from edulelis/migrate-to-better-auth
edulelis Aug 5, 2025
95a4bde
Use old redirect URI. Remove extra envs
edulelis Aug 5, 2025
172295f
Update migration in place. Update schema
edulelis Aug 5, 2025
9b58562
Update migration
edulelis Aug 5, 2025
4709315
Merge pull request #638 from edulelis/migrate-to-better-auth
edulelis Aug 5, 2025
61e5552
Fix typo in redirect uri param
edulelis Aug 5, 2025
6a515d9
Merge pull request #639 from edulelis/migrate-to-better-auth
edulelis Aug 5, 2025
6a61319
Update base paths
edulelis Aug 5, 2025
44ba462
Merge pull request #640 from edulelis/migrate-to-better-auth
edulelis Aug 5, 2025
05608b8
Enable logging. Add trusted origins
edulelis Aug 5, 2025
598d302
Merge pull request #641 from edulelis/migrate-to-better-auth
edulelis Aug 5, 2025
2c700dd
Remove custom redirectURI
edulelis Aug 5, 2025
80bbf29
Merge pull request #642 from edulelis/migrate-to-better-auth
edulelis Aug 5, 2025
2e0a05f
logs with patched pkg
edulelis Aug 5, 2025
b2af57a
Update scopes. Add baseURL
edulelis Aug 5, 2025
3ba416e
Merge pull request #643 from edulelis/migrate-to-better-auth
edulelis Aug 5, 2025
a542e91
Delete patches
edulelis Aug 5, 2025
14eddb7
Restore lockfile
edulelis Aug 5, 2025
8545245
Merge pull request #644 from edulelis/migrate-to-better-auth
edulelis Aug 5, 2025
a34c7d0
Update package.json and lockfile
edulelis Aug 5, 2025
da16d07
Merge pull request #646 from edulelis/migrate-to-better-auth
edulelis Aug 5, 2025
781b297
Log levels and keep verification for debugging
edulelis Aug 6, 2025
f10918c
Merge pull request #647 from edulelis/migrate-to-better-auth
edulelis Aug 6, 2025
c4e14d8
Disable ip tracking. Log debug as info
edulelis Aug 6, 2025
c308581
Merge pull request #649 from edulelis/migrate-to-better-auth
edulelis Aug 6, 2025
e793528
Cookie domain setup
edulelis Aug 6, 2025
e7851db
Merge pull request #651 from edulelis/migrate-to-better-auth
edulelis Aug 6, 2025
f4cc018
Update cooki settings. Add cookie env
edulelis Aug 6, 2025
4a791d5
Merge pull request #653 from edulelis/migrate-to-better-auth
edulelis Aug 6, 2025
f59168b
Remove auto logout
edulelis Aug 6, 2025
1ffefaa
Merge pull request #654 from edulelis/migrate-to-better-auth
edulelis Aug 6, 2025
f2cefa6
Test simplified auth flow
edulelis Aug 6, 2025
243a6a1
Merge pull request #655 from edulelis/migrate-to-better-auth
edulelis Aug 6, 2025
59053e4
Major better-auth refactor
edulelis Aug 6, 2025
00be0b5
Remove unused props
edulelis Aug 6, 2025
4dee238
PR cleanup
edulelis Aug 6, 2025
0f23b94
Merge pull request #658 from edulelis/migrate-to-better-auth
edulelis Aug 6, 2025
4bc0674
Merge branch 'main' of github.com:elie222/inbox-zero into migrate-to-…
edulelis Aug 7, 2025
15d4dab
Cleanup unused props. Add linking on update
edulelis Aug 7, 2025
42f724b
PR feedback
edulelis Aug 7, 2025
a800c2c
PR Feedback
edulelis Aug 7, 2025
b5468fe
Merge pull request #662 from edulelis/migrate-to-better-auth
edulelis Aug 7, 2025
e35609f
Fix tests
edulelis Aug 7, 2025
6d539c3
Initial commit move to folder action
edulelis Aug 10, 2025
af8992f
Remove unused imports
edulelis Aug 10, 2025
2c93da7
Merge branch 'main' of github.com:elie222/inbox-zero into move-to-fol…
edulelis Aug 10, 2025
bfe6890
Type fixes after move to folder action
edulelis Aug 10, 2025
2da5a75
Merge branch 'main' of github.com:elie222/inbox-zero into move-to-fol…
edulelis Aug 11, 2025
26bdc0a
Merge branch 'main' of github.com:elie222/inbox-zero into merge-main-…
edulelis Aug 11, 2025
060753f
Update microsoft provider
edulelis Aug 11, 2025
175e073
Merge pull request #669 from edulelis/merge-main-staging
edulelis Aug 11, 2025
f5d775e
Merge branch 'staging' of github.com:elie222/inbox-zero into move-to-…
edulelis Aug 11, 2025
8e1aa36
Schema fixes. Add move to folder migration. PR feedback fixes
edulelis Aug 11, 2025
e98db3e
Merge pull request #668 from edulelis/move-to-folder-action
edulelis Aug 11, 2025
6eb4fd5
Merge branch 'main' of github.com:elie222/inbox-zero into migrate-to-…
edulelis Aug 11, 2025
5e7efc6
Update logs
edulelis Aug 11, 2025
4f66b97
outlook security
elie222 Aug 11, 2025
8f8534f
fix github action tests
elie222 Aug 11, 2025
3cd6c9b
PR feedback
edulelis Aug 11, 2025
6692fb7
Merge branch 'staging' of github.com:elie222/inbox-zero into migrate-…
edulelis Aug 11, 2025
bd4ae64
PR feedback
edulelis Aug 11, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ jobs:
NEXTAUTH_SECRET: "secret"
GOOGLE_CLIENT_ID: "client_id"
GOOGLE_CLIENT_SECRET: "client_secret"
MICROSOFT_CLIENT_ID: "client_id"
MICROSOFT_CLIENT_SECRET: "client_secret"
GOOGLE_PUBSUB_TOPIC_NAME: "topic"
EMAIL_ENCRYPT_SECRET: "secret"
EMAIL_ENCRYPT_SALT: "salt"
INTERNAL_API_KEY: "secret"
INTERNAL_API_KEY: "secret"
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,9 @@ Create [new credentials](https://console.cloud.google.com/apis/credentials):
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`
5. In `Authorized redirect URIs` enter:
- `http://localhost:3000/api/auth/callback/google`
- `http://localhost:3000/api/google/linking/callback`
6. Click `Create`.
7. A popup will show up with the new credentials, including the Client ID and secret.
3. Update .env file:
Expand Down
1 change: 1 addition & 0 deletions apps/web/__tests__/ai-choose-rule.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ describe.runIf(isAiTest)("aiChooseRule", () => {
cc: null,
bcc: null,
url: null,
folderName: null,
delayInMinutes: null,
},
]);
Expand Down
58 changes: 58 additions & 0 deletions apps/web/__tests__/outlook-odata-escape.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { describe, it, expect } from "vitest";
import { escapeODataString } from "@/utils/outlook/odata-escape";

describe("OData String Escaping", () => {
it("should escape single quotes by doubling them", () => {
expect(escapeODataString("O'Brien")).toBe("O''Brien");
expect(escapeODataString("test' or 1=1 --")).toBe("test'' or 1=1 --");
expect(escapeODataString("it's a test")).toBe("it''s a test");
});

it("should handle strings without quotes", () => {
expect(escapeODataString("normal string")).toBe("normal string");
expect(escapeODataString("test@example.com")).toBe("test@example.com");
});

it("should handle multiple quotes", () => {
expect(escapeODataString("'test'")).toBe("''test''");
expect(escapeODataString("test's 'quoted' text")).toBe(
"test''s ''quoted'' text",
);
});

it("should handle empty strings", () => {
expect(escapeODataString("")).toBe("");
});

it("should handle non-string inputs safely", () => {
expect(escapeODataString(null as any)).toBe("");
expect(escapeODataString(undefined as any)).toBe("");
expect(escapeODataString(123 as any)).toBe("");
});

it("should prevent OData injection attacks", () => {
// Simulated malicious inputs
const maliciousEmail = "attacker@example.com' or subject eq 'sensitive";
const escaped = escapeODataString(maliciousEmail);

// The escaped version should have doubled quotes
expect(escaped).toBe("attacker@example.com'' or subject eq ''sensitive");

// When used in a filter, it should be safe
const filter = `from/emailAddress/address eq '${escaped}'`;
expect(filter).toBe(
"from/emailAddress/address eq 'attacker@example.com'' or subject eq ''sensitive'",
);

// The filter should not allow breaking out of the string literal
// Check that there are no unescaped single quotes followed by " or "
// (All quotes should be doubled, so we shouldn't see a single quote followed by " or ")
expect(filter).toContain(
"attacker@example.com'' or subject eq ''sensitive",
);
// Verify the malicious pattern has been neutralized
expect(filter).toBe(
"from/emailAddress/address eq 'attacker@example.com'' or subject eq ''sensitive'",
);
});
});
2 changes: 1 addition & 1 deletion apps/web/app/(app)/ErrorMessages.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { auth } from "@/app/api/auth/[...nextauth]/auth";
import { auth } from "@/utils/auth";
import { AlertError } from "@/components/Alert";
import { Button } from "@/components/ui/button";
import { clearUserErrorMessagesAction } from "@/utils/actions/error-messages";
Expand Down
2 changes: 1 addition & 1 deletion apps/web/app/(app)/[emailAccountId]/assess.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export function AssessUser() {
async function assess() {
const result = await executeAssessAsync();
// no need to run this over and over after the first time
if (!result?.data?.skipped && provider !== "microsoft-entra-id") {
if (!result?.data?.skipped && provider !== "microsoft") {
executeWhitelistInboxZero();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,10 @@ export function ActionSummaryCard({
summaryContent = "Add to digest";
break;

case ActionType.MOVE_FOLDER:
summaryContent = `Folder: ${action.folderName?.value || "unset"}`;
break;

default:
summaryContent = actionTypeLabel;
}
Expand Down
19 changes: 16 additions & 3 deletions apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ export function RuleForm({
isDialog?: boolean;
mutate?: (data?: any, options?: any) => void;
}) {
const { emailAccountId } = useAccount();
const { emailAccountId, provider } = useAccount();

const form = useForm<CreateRuleBody>({
resolver: zodResolver(createRuleBody),
Expand All @@ -140,6 +140,7 @@ export function RuleForm({
...action.content,
setManually: !!action.content?.value,
},
folderName: action.folderName,
})),
],
}
Expand Down Expand Up @@ -319,9 +320,19 @@ export function RuleForm({
const conditionalOperator = watch("conditionalOperator");

const typeOptions = useMemo(() => {
return [
const providerOptions: { label: string; value: ActionType }[] = [];

if (provider === "microsoft") {
providerOptions.push({
label: "Move to folder",
value: ActionType.MOVE_FOLDER,
});
}
Comment on lines +325 to +330
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i would just inline as anyway if things will be added in the future it's unlikely in the next spot after move to folder


const options = [
{ label: "Archive", value: ActionType.ARCHIVE },
{ label: "Label", value: ActionType.LABEL },
...providerOptions,
{ label: "Draft reply", value: ActionType.DRAFT_EMAIL },
{ label: "Reply", value: ActionType.REPLY },
{ label: "Send email", value: ActionType.SEND_EMAIL },
Expand All @@ -332,7 +343,9 @@ export function RuleForm({
{ label: "Call webhook", value: ActionType.CALL_WEBHOOK },
{ label: "Auto-update reply label", value: ActionType.TRACK_THREAD },
];
}, []);

return options;
}, [provider]);

const [isNameEditMode, setIsNameEditMode] = useState(alwaysEditMode);
const [isConditionsEditMode, setIsConditionsEditMode] =
Expand Down
4 changes: 4 additions & 0 deletions apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ export function Rules({ size = "md" }: { size?: "sm" | "md" }) {
cc: null,
bcc: null,
url: null,
folderName: null,
delayInMinutes: null,
},
showArchiveAction
Expand All @@ -149,6 +150,7 @@ export function Rules({ size = "md" }: { size?: "sm" | "md" }) {
cc: null,
bcc: null,
url: null,
folderName: null,
delayInMinutes: null,
}
: null,
Expand All @@ -166,6 +168,7 @@ export function Rules({ size = "md" }: { size?: "sm" | "md" }) {
cc: null,
bcc: null,
url: null,
folderName: null,
delayInMinutes: null,
}
: null,
Expand Down Expand Up @@ -464,6 +467,7 @@ export function ActionBadges({
id: string;
type: ActionType;
label?: string | null;
folderName?: string | null;
}[];
}) {
return (
Expand Down
7 changes: 7 additions & 0 deletions apps/web/app/(app)/[emailAccountId]/assistant/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
WebhookIcon,
EyeIcon,
FileTextIcon,
FolderInputIcon,
} from "lucide-react";
import { ActionType } from "@prisma/client";

Expand All @@ -25,6 +26,7 @@ const ACTION_TYPE_COLORS = {
[ActionType.CALL_WEBHOOK]: "bg-gray-500",
[ActionType.TRACK_THREAD]: "bg-indigo-500",
[ActionType.DIGEST]: "bg-teal-500",
[ActionType.MOVE_FOLDER]: "bg-emerald-500",
} as const;

export const ACTION_TYPE_TEXT_COLORS = {
Expand All @@ -39,6 +41,7 @@ export const ACTION_TYPE_TEXT_COLORS = {
[ActionType.CALL_WEBHOOK]: "text-gray-500",
[ActionType.TRACK_THREAD]: "text-indigo-500",
[ActionType.DIGEST]: "text-teal-500",
[ActionType.MOVE_FOLDER]: "text-emerald-500",
} as const;

export const ACTION_TYPE_ICONS = {
Expand All @@ -53,6 +56,7 @@ export const ACTION_TYPE_ICONS = {
[ActionType.CALL_WEBHOOK]: WebhookIcon,
[ActionType.TRACK_THREAD]: EyeIcon,
[ActionType.DIGEST]: FileTextIcon,
[ActionType.MOVE_FOLDER]: FolderInputIcon,
} as const;

// Helper function to get action type from string (for RulesPrompt.tsx)
Expand Down Expand Up @@ -83,6 +87,9 @@ export function getActionTypeColor(example: string): string {
if (lowerExample.includes("digest")) {
return ACTION_TYPE_COLORS[ActionType.DIGEST];
}
if (lowerExample.includes("folder")) {
return ACTION_TYPE_COLORS[ActionType.MOVE_FOLDER];
}

// Default fallback
return "bg-gray-500";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ export function useDraftReplies() {
bcc: null,
url: null,
delayInMinutes: null,
folderName: null,
createdAt: new Date(),
updatedAt: new Date(),
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
import { Badge } from "@/components/ui/badge";
import { notFound } from "next/navigation";
import { formatDistanceToNow } from "date-fns";
import { auth } from "@/app/api/auth/[...nextauth]/auth";
import { auth } from "@/utils/auth";

export default async function RuleHistoryPage(props: {
params: Promise<{ emailAccountId: string; ruleId: string }>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { useCallback } from "react";
import { type SubmitHandler, useFieldArray, useForm } from "react-hook-form";
import { useSession } from "next-auth/react";
import { useSession } from "@/utils/auth-client";
import { zodResolver } from "@hookform/resolvers/zod";
import useSWR from "swr";
import { usePostHog } from "posthog-js/react";
Expand Down
17 changes: 12 additions & 5 deletions apps/web/app/(app)/accounts/AddAccount.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import { useState } from "react";
import { signIn } from "next-auth/react";
import { signIn } from "@/utils/auth-client";
import { Card, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { toastError } from "@/components/Toast";
Expand All @@ -14,10 +14,16 @@ import { DialogTrigger } from "@/components/ui/dialog";
import { Dialog } from "@/components/ui/dialog";
import type { GetAuthLinkUrlResponse } from "@/app/api/google/linking/auth-url/route";
import type { GetOutlookAuthLinkUrlResponse } from "@/app/api/outlook/linking/auth-url/route";
import { SCOPES as GMAIL_SCOPES } from "@/utils/gmail/scopes";
import { SCOPES as OUTLOOK_SCOPES } from "@/utils/outlook/scopes";

export function AddAccount() {
const handleConnectGoogle = async () => {
await signIn("google", { callbackUrl: "/accounts", redirect: true });
await signIn.social({
provider: "google",
callbackURL: "/accounts",
scopes: [...GMAIL_SCOPES],
});
};

const handleMergeGoogle = async () => {
Expand All @@ -32,9 +38,10 @@ export function AddAccount() {
};

const handleConnectMicrosoft = async () => {
await signIn("microsoft-entra-id", {
callbackUrl: "/accounts",
redirect: true,
await signIn.social({
provider: "microsoft",
callbackURL: "/accounts",
scopes: [...OUTLOOK_SCOPES],
});
};

Expand Down
2 changes: 1 addition & 1 deletion apps/web/app/(app)/admin/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { AdminUpgradeUserForm } from "@/app/(app)/admin/AdminUpgradeUserForm";
import { AdminUserControls } from "@/app/(app)/admin/AdminUserControls";
import { TopSection } from "@/components/TopSection";
import { auth } from "@/app/api/auth/[...nextauth]/auth";
import { auth } from "@/utils/auth";
import { ErrorPage } from "@/components/ErrorPage";
import { isAdmin } from "@/utils/admin";
import {
Expand Down
6 changes: 3 additions & 3 deletions apps/web/app/(app)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { cookies } from "next/headers";
import { Suspense } from "react";
import { redirect } from "next/navigation";
import { SideNavWithTopNav } from "@/components/SideNavWithTopNav";
import { TokenCheck } from "@/components/TokenCheck";
import { auth } from "@/app/api/auth/[...nextauth]/auth";

import { auth } from "@/utils/auth";
import { PostHogIdentify } from "@/providers/PostHogProvider";
import { CommandK } from "@/components/CommandK";
import { AppProviders } from "@/providers/AppProviders";
Expand Down Expand Up @@ -50,7 +50,7 @@ export default async function AppLayout({
<EmailViewer />
<ErrorBoundary extra={{ component: "AppLayout" }}>
<PostHogIdentify />
<TokenCheck />

<CommandK />
<QueueInitializer />
<AssessUser />
Expand Down
Loading
Loading