Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
35 changes: 19 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,13 @@ docker run -d --name dory \
-e DORY_AI_MODEL=gpt-4o-mini \
-e DORY_AI_API_KEY=your_api_key_here \
-e DORY_AI_URL=https://api.openai.com/v1 \
-e NEXT_PUBLIC_REQUIRE_EMAIL_VERIFICATION=false \
dorylab/dory:latest

```

To enable email verification, set `RESEND_API_KEY` to a valid [resend](https://resend.com) key and `EMAIL_FROM` to a validated email.


### 🧠 Supported AI Providers

Expand All @@ -62,14 +65,14 @@ You can freely switch between different model vendors by changing environment va

Currently supported providers:

| Provider | Env `DORY_AI_PROVIDER` | Description |
|----------|-------------------------|-------------|
| OpenAI | `openai` | Default provider. Uses official OpenAI API. |
| OpenAI-Compatible | `openai-compatible` | Any service exposing an OpenAI-compatible API. |
| Anthropic | `anthropic` | Claude models via Anthropic official API. |
| Google | `google` | Gemini models via Google Generative AI API. |
| Qwen (Alibaba) | `qwen` | Qwen models via DashScope OpenAI-compatible endpoint. |
| xAI | `xai` | Grok models via xAI API. |
| Provider | Env `DORY_AI_PROVIDER` | Description |
| ----------------- | ---------------------- | ----------------------------------------------------- |
| OpenAI | `openai` | Default provider. Uses official OpenAI API. |
| OpenAI-Compatible | `openai-compatible` | Any service exposing an OpenAI-compatible API. |
| Anthropic | `anthropic` | Claude models via Anthropic official API. |
| Google | `google` | Gemini models via Google Generative AI API. |
| Qwen (Alibaba) | `qwen` | Qwen models via DashScope OpenAI-compatible endpoint. |
| xAI | `xai` | Grok models via xAI API. |


## ✨ Key Features
Expand Down Expand Up @@ -153,14 +156,14 @@ Native ClickHouse user and role management UI.

## 🔌 Database Support

| Database | Status |
|--------------|---------------------|
| ClickHouse | ✅ Deeply integrated |
| PostgreSQL | ✅ Supported |
| MySQL | ✅ Supported |
| MariaDB | ✅ Supported | |
| SQLite | ✅ Supported (Beta) |
| DuckDB | 🚧 Planned
| Database | Status |
| ---------- | ------------------- |
| ClickHouse | ✅ Deeply integrated |
| PostgreSQL | ✅ Supported |
| MySQL | ✅ Supported |
| MariaDB | ✅ Supported | |
| SQLite | ✅ Supported (Beta) |
| DuckDB | 🚧 Planned |

---

Expand Down
17 changes: 13 additions & 4 deletions apps/web/app/(auth)/components/SignUpform.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import { VerifyEmailPanel } from './verify-email-panel';
import { Card, CardContent } from '@/registry/new-york-v4/ui/card';
import { useTranslations } from 'next-intl';
import { IconBrandGithub } from '@tabler/icons-react';
import { parseEnvFlag } from '@/lib/env';
import { env } from 'next-runtime-env';

type Stage = 'form' | 'verify';

Expand All @@ -23,6 +25,7 @@ type SignUpFormProps = React.ComponentProps<'div'> & {

export function SignUpForm({ className, callbackURL: callbackURLOverride, onRequestSignIn, ...props }: SignUpFormProps) {
const t = useTranslations('Auth');
const requireEmailVerification = parseEnvFlag(env('NEXT_PUBLIC_REQUIRE_EMAIL_VERIFICATION'));
const [loading, setLoading] = useState(false);
const [err, setErr] = useState<string | null>(null);
const [stage, setStage] = useState<Stage>('form');
Expand Down Expand Up @@ -55,16 +58,22 @@ export function SignUpForm({ className, callbackURL: callbackURLOverride, onRequ
if (!error) {
posthog.identify(email, { email, name });
posthog.capture('user_signed_up', { method: 'email' });
setEmailForVerify(email);
setStage('verify');
if (requireEmailVerification) {
setEmailForVerify(email);
setStage('verify');
} else {
window.location.href = callbackURL;
}
} else {
// Keep user on the form for regular errors (e.g. email already exists).
// Only switch for "unverified account" type errors.
if (verifyMatchRegex.test(error.message ?? '')) {
posthog.identify(email, { email, name });
posthog.capture('user_signed_up', { method: 'email' });
setEmailForVerify(email);
setStage('verify');
if (requireEmailVerification) {
setEmailForVerify(email);
setStage('verify');
}
} else {
posthog.capture('user_sign_up_failed', { method: 'email', error: error.message });
}
Expand Down
7 changes: 5 additions & 2 deletions apps/web/lib/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { getClient } from './database/postgres/client';
import { getDatabaseProvider } from './database/provider';
import { schema } from './database/schema';
import { sendEmail } from './email';
import { parseEnvFlag } from './env';
import { resolveOrganizationIdForSession, shouldCreateDefaultOrganization } from './auth/migration-state';
import { createProvisionedOrganization } from './auth/organization-provisioning';
import { translate } from './i18n/i18n';
Expand All @@ -22,6 +23,8 @@ import { canManageOrganizationBilling } from './billing/authz';
import { buildDefaultOrganizationValues, linkAnonymousOrganizationToUser } from './auth/anonymous';
import { isAnonymousUser } from './auth/anonymous-user';

const REQUIRE_EMAIL_VERIFICATION = parseEnvFlag(process.env.NEXT_PUBLIC_REQUIRE_EMAIL_VERIFICATION);

type AuthUser = {
id: string;
email: string | null;
Expand Down Expand Up @@ -498,7 +501,7 @@ function createAuth() {

emailAndPassword: {
enabled: true,
requireEmailVerification: true,
requireEmailVerification: REQUIRE_EMAIL_VERIFICATION,
autoSignInAfterVerification: true,

sendResetPassword: async ({ user, url, token }, request) => {
Expand Down Expand Up @@ -538,7 +541,7 @@ function createAuth() {
},

emailVerification: {
sendOnSignUp: true,
sendOnSignUp: REQUIRE_EMAIL_VERIFICATION,

sendVerificationEmail: async ({ user, url, token }, request) => {
const locale = await getServerLocale();
Expand Down
5 changes: 3 additions & 2 deletions apps/web/lib/email.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ if (!RESEND_API_KEY) {
}
const resend = new Resend(RESEND_API_KEY);

const EMAIL_FROM = process.env.EMAIL_FROM || 'Dory<noreply@getdory.dev>';

export async function sendEmail({
to,
subject,
Expand All @@ -18,8 +20,7 @@ export async function sendEmail({
text: string;
html?: string;
}) {
// Sender must use a verified domain/subdomain and include a friendly name
const from = 'Dory<noreply@getdory.dev>'; // Example: your verified subdomain
const from = EMAIL_FROM;
logger.info(`[email] payload to=${to} subject="${subject}" html=${Boolean(html)} htmlLen=${html?.length || 0} textLen=${text?.length || 0}`);
try {
const { data, error } = await resend.emails.send({
Expand Down
8 changes: 8 additions & 0 deletions apps/web/lib/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* Parses an environment variable as a boolean flag.
* Returns `defaultValue` when the variable is undefined; `false` when the value is the string `"false"`.
*/
export function parseEnvFlag(value: string | undefined, defaultValue = true): boolean {
if (value === undefined) return defaultValue;
return value !== 'false';
}