mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
Merge branch 'main' into dev
This commit is contained in:
@@ -15,6 +15,15 @@ PROJECT_NAME="Portabase"
|
||||
PROJECT_URL=http://localhost:8887
|
||||
PROJECT_SECRET=
|
||||
|
||||
AUTH_DEFAULT_USER_NAME="Portabase Admin"
|
||||
AUTH_DEFAULT_USER=user@example.com
|
||||
# Password must contain at least 8 characters
|
||||
# Password must contain at least 1 number
|
||||
# Password must contain at least 1 lowercase letter
|
||||
# Password must contain at least 1 uppercase letter
|
||||
# Password must contain at least 1 special character
|
||||
AUTH_DEFAULT_PASSWORD=testPASS123456!
|
||||
|
||||
# SMTP (email)
|
||||
SMTP_HOST=
|
||||
SMTP_PORT=
|
||||
|
||||
+1
-1
@@ -33,5 +33,5 @@ keywords:
|
||||
- web-ui
|
||||
- agent
|
||||
license: Apache-2.0
|
||||
version: 1.15.0
|
||||
version: 1.15.3
|
||||
date-released: '2026-03-02'
|
||||
|
||||
@@ -30,9 +30,11 @@ export default async function RoutePage(props: PageParams<{}>) {
|
||||
<PageHeader className="flex flex-col">
|
||||
<div className="flex justify-between">
|
||||
<PageTitle className="mb-3">Active users</PageTitle>
|
||||
<PageActions>
|
||||
<AdminUserAddModal organizations={organizations}/>
|
||||
</PageActions>
|
||||
{isPasswordAuthEnabled && (
|
||||
<PageActions>
|
||||
<AdminUserAddModal organizations={organizations}/>
|
||||
</PageActions>
|
||||
)}
|
||||
</div>
|
||||
</PageHeader>
|
||||
<PageContent className="flex flex-col gap-5">
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "portabase",
|
||||
"version": "1.15.0",
|
||||
"version": "1.15.3",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev --turbopack -p 8887",
|
||||
@@ -136,5 +136,5 @@
|
||||
"typescript": "^5.9.3",
|
||||
"zenstack": "2.14.2"
|
||||
},
|
||||
"packageManager": "pnpm@11.1.2+sha512.415a1cc25974731e75455c1468371be74c5aa5fb7621b50d4056d222451609f11412f23fd602e6169f1e060466641f798597e1be961a10688836a67b16569499"
|
||||
"packageManager": "pnpm@11.1.3"
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ export const createUserAction = userAction
|
||||
email: parsedInput.email,
|
||||
theme: "dark",
|
||||
role: "user",
|
||||
password: ""
|
||||
};
|
||||
|
||||
if (isPasswordAuthEnabled) {
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE "backups" ALTER COLUMN "file_size" SET DATA TYPE bigint;--> statement-breakpoint
|
||||
ALTER TABLE "backup_storage" ALTER COLUMN "size" SET DATA TYPE bigint;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -386,6 +386,13 @@
|
||||
"when": 1778523056918,
|
||||
"tag": "0054_hesitant_darkhawk",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 55,
|
||||
"version": "7",
|
||||
"when": 1779342014802,
|
||||
"tag": "0055_yielding_justin_hammer",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import {pgTable, text, boolean, timestamp, uuid, integer, pgEnum} from "drizzle-orm/pg-core";
|
||||
import {pgTable, text, boolean, timestamp, uuid, integer, pgEnum, bigint} from "drizzle-orm/pg-core";
|
||||
import {Agent, agent, AgentWith} from "./08_agent";
|
||||
import {Project, project} from "./06_project";
|
||||
import {relations} from "drizzle-orm";
|
||||
@@ -37,7 +37,7 @@ export const backup = pgTable(
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
status: statusEnum("status").default("waiting").notNull(),
|
||||
file: text("file"),
|
||||
fileSize: integer("file_size"),
|
||||
fileSize: bigint("file_size", { mode: "number" }),
|
||||
databaseId: uuid("database_id")
|
||||
.notNull()
|
||||
.references(() => database.id, {onDelete: "cascade"}),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { pgTable, uuid, text, integer, pgEnum } from "drizzle-orm/pg-core";
|
||||
import {pgTable, uuid, text, integer, pgEnum, bigint} from "drizzle-orm/pg-core";
|
||||
import { timestamps } from "@/db/schema/00_common";
|
||||
import {StorageChannel, storageChannel} from "@/db/schema/12_storage-channel";
|
||||
import {Backup, backup, Restoration} from "@/db/schema/07_database";
|
||||
@@ -22,7 +22,7 @@ export const backupStorage = pgTable("backup_storage", {
|
||||
.references(() => storageChannel.id, { onDelete: "cascade" }),
|
||||
status: backupStorageStatusEnum("status").notNull().default("pending"),
|
||||
path: text("path"),
|
||||
size: integer("size"),
|
||||
size: bigint("size", { mode: "number" }),
|
||||
checksum: text("checksum"),
|
||||
...timestamps,
|
||||
});
|
||||
|
||||
@@ -3,13 +3,17 @@ import {hashPassword} from "better-auth/crypto";
|
||||
import {db} from "@/db";
|
||||
import * as drizzleDb from "@/db";
|
||||
import {User, UserThemeEnum} from "@/db/schema/02_user";
|
||||
import {assertValidPassword} from "@/utils/password";
|
||||
|
||||
|
||||
export async function createUserDb(data: SignUpUser): Promise<User> {
|
||||
assertValidPassword(data.password);
|
||||
|
||||
const now = new Date();
|
||||
const userId = crypto.randomUUID();
|
||||
|
||||
const [newUser] = await db.insert(drizzleDb.schemas.user).values({
|
||||
...data,
|
||||
id: userId,
|
||||
name: data.name,
|
||||
email: data.email,
|
||||
|
||||
+134
-127
@@ -1,167 +1,174 @@
|
||||
import { createEnv } from "@t3-oss/env-nextjs";
|
||||
import {createEnv} from "@t3-oss/env-nextjs";
|
||||
import path from "path";
|
||||
import { z } from "zod";
|
||||
import packageJson from "../package.json" with { type: "json" };
|
||||
import {z} from "zod";
|
||||
import packageJson from "../package.json" with {type: "json"};
|
||||
|
||||
const { version } = packageJson;
|
||||
const {version} = packageJson;
|
||||
|
||||
export const env = createEnv({
|
||||
server: {
|
||||
NEXT_PUBLIC_PROJECT_VERSION: z.string().optional(),
|
||||
server: {
|
||||
NEXT_PUBLIC_PROJECT_VERSION: z.string().optional(),
|
||||
|
||||
NODE_ENV: z.enum(["development", "production"]).optional(),
|
||||
LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]).default("info"),
|
||||
NODE_ENV: z.enum(["development", "production"]).optional(),
|
||||
LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]).default("info"),
|
||||
|
||||
DATABASE_URL: z.string().url().optional(),
|
||||
DATABASE_URL: z.url().optional(),
|
||||
|
||||
PROJECT_NAME: z.string().optional(),
|
||||
PROJECT_DESCRIPTION: z.string().optional(),
|
||||
PROJECT_URL: z
|
||||
.string()
|
||||
.regex(/^https?:\/\//, "URL must start with http:// or https://"),
|
||||
PROJECT_SECRET: z.string(),
|
||||
PROJECT_NAME: z.string().optional(),
|
||||
PROJECT_DESCRIPTION: z.string().optional(),
|
||||
PROJECT_URL: z
|
||||
.string()
|
||||
.regex(/^https?:\/\//, "URL must start with http:// or https://"),
|
||||
PROJECT_SECRET: z.string(),
|
||||
|
||||
TRUSTED_DOMAINS: z.string().optional(),
|
||||
TRUSTED_DOMAINS: z.string().optional(),
|
||||
|
||||
SMTP_PASSWORD: z.string().optional(),
|
||||
SMTP_FROM: z.string().optional(),
|
||||
SMTP_HOST: z.string().optional(),
|
||||
SMTP_PORT: z.string().optional(),
|
||||
SMTP_USER: z.string().optional(),
|
||||
SMTP_PASSWORD: z.string().optional(),
|
||||
SMTP_FROM: z.string().optional(),
|
||||
SMTP_HOST: z.string().optional(),
|
||||
SMTP_PORT: z.string().optional(),
|
||||
SMTP_USER: z.string().optional(),
|
||||
|
||||
AUTH_DEFAULT_USER_NAME: z.string().optional(),
|
||||
AUTH_DEFAULT_USER: z.string().optional(),
|
||||
AUTH_DEFAULT_PASSWORD: z.string().optional(),
|
||||
|
||||
|
||||
SMTP_SECURE: z
|
||||
.enum(["true", "false"])
|
||||
.transform((val) => val === "true")
|
||||
.default("true"),
|
||||
SMTP_SECURE: z
|
||||
.enum(["true", "false"])
|
||||
.transform((val) => val === "true")
|
||||
.default("true"),
|
||||
|
||||
AUTH_GOOGLE_ID: z.string().optional(),
|
||||
AUTH_GOOGLE_SECRET: z.string().optional(),
|
||||
AUTH_GOOGLE_ID: z.string().optional(),
|
||||
AUTH_GOOGLE_SECRET: z.string().optional(),
|
||||
|
||||
AUTH_GITHUB_ID: z.string().optional(),
|
||||
AUTH_GITHUB_SECRET: z.string().optional(),
|
||||
AUTH_GITHUB_ID: z.string().optional(),
|
||||
AUTH_GITHUB_SECRET: z.string().optional(),
|
||||
|
||||
RETENTION_CRON: z
|
||||
.string()
|
||||
.default(
|
||||
process.env.NODE_ENV === "production" ? "0 7 * * *" : "* * * * *",
|
||||
),
|
||||
RETENTION_CRON: z
|
||||
.string()
|
||||
.default(
|
||||
process.env.NODE_ENV === "production" ? "0 7 * * *" : "* * * * *",
|
||||
),
|
||||
|
||||
CLEANING_HEALTHCHECK_LOGS_CRON: z
|
||||
.string()
|
||||
.default(
|
||||
process.env.NODE_ENV === "production" ? "0 * * * *" : "* * * * *",
|
||||
),
|
||||
CLEANING_HEALTHCHECK_LOGS_CRON: z
|
||||
.string()
|
||||
.default(
|
||||
process.env.NODE_ENV === "production" ? "0 * * * *" : "* * * * *",
|
||||
),
|
||||
|
||||
HEALTHCHECK_CRON: z
|
||||
.string()
|
||||
.default(
|
||||
process.env.NODE_ENV === "production" ? "0 * * * *" : "* * * * *",
|
||||
),
|
||||
HEALTHCHECK_CRON: z
|
||||
.string()
|
||||
.default(
|
||||
process.env.NODE_ENV === "production" ? "0 * * * *" : "* * * * *",
|
||||
),
|
||||
|
||||
|
||||
AUTH_OIDC_ID: z.string().optional().default("oidc"),
|
||||
AUTH_OIDC_TITLE: z.string().optional(),
|
||||
AUTH_OIDC_DESC: z.string().optional(),
|
||||
AUTH_OIDC_ICON: z.string().optional(),
|
||||
AUTH_OIDC_CLIENT: z.string().optional(),
|
||||
AUTH_OIDC_SECRET: z.string().optional(),
|
||||
AUTH_OIDC_ISSUER_URL: z.string().optional(),
|
||||
AUTH_OIDC_HOST: z.string().optional(),
|
||||
AUTH_OIDC_SCOPES: z.string().optional(),
|
||||
AUTH_OIDC_DISCOVERY_ENDPOINT: z.string().optional(),
|
||||
AUTH_OIDC_JWKS_ENDPOINT: z.string().optional(),
|
||||
AUTH_OIDC_PKCE: z.string().optional(),
|
||||
|
||||
AUTH_OIDC_ID: z.string().optional().default("oidc"),
|
||||
AUTH_OIDC_TITLE: z.string().optional(),
|
||||
AUTH_OIDC_DESC: z.string().optional(),
|
||||
AUTH_OIDC_ICON: z.string().optional(),
|
||||
AUTH_OIDC_CLIENT: z.string().optional(),
|
||||
AUTH_OIDC_SECRET: z.string().optional(),
|
||||
AUTH_OIDC_ISSUER_URL: z.string().optional(),
|
||||
AUTH_OIDC_HOST: z.string().optional(),
|
||||
AUTH_OIDC_SCOPES: z.string().optional(),
|
||||
AUTH_OIDC_DISCOVERY_ENDPOINT: z.string().optional(),
|
||||
AUTH_OIDC_JWKS_ENDPOINT: z.string().optional(),
|
||||
AUTH_OIDC_PKCE: z.string().optional(),
|
||||
AUTH_SOCIAL_ID: z.string().optional().default("social"),
|
||||
AUTH_SOCIAL_TITLE: z.string().optional(),
|
||||
AUTH_SOCIAL_DESC: z.string().optional(),
|
||||
AUTH_SOCIAL_ICON: z.string().optional(),
|
||||
AUTH_SOCIAL_CLIENT: z.string().optional(),
|
||||
AUTH_SOCIAL_SECRET: z.string().optional(),
|
||||
AUTH_SOCIAL_APPLE_APP_BUNDLE_IDENTIFIER: z.string().optional(),
|
||||
|
||||
AUTH_SOCIAL_ID: z.string().optional().default("social"),
|
||||
AUTH_SOCIAL_TITLE: z.string().optional(),
|
||||
AUTH_SOCIAL_DESC: z.string().optional(),
|
||||
AUTH_SOCIAL_ICON: z.string().optional(),
|
||||
AUTH_SOCIAL_CLIENT: z.string().optional(),
|
||||
AUTH_SOCIAL_SECRET: z.string().optional(),
|
||||
AUTH_SOCIAL_APPLE_APP_BUNDLE_IDENTIFIER: z.string().optional(),
|
||||
ALLOWED_GROUP: z.string().optional(),
|
||||
|
||||
ALLOWED_GROUP: z.string().optional(),
|
||||
AUTH_EMAIL_PASSWORD_ENABLED: z.string().optional().default("true"),
|
||||
AUTH_SIGNUP_ENABLED: z.string().optional().default("true"),
|
||||
AUTH_PASSKEY_ENABLED: z.string().optional().default("false"),
|
||||
|
||||
AUTH_EMAIL_PASSWORD_ENABLED: z.string().optional().default("true"),
|
||||
AUTH_SIGNUP_ENABLED: z.string().optional().default("true"),
|
||||
AUTH_PASSKEY_ENABLED: z.string().optional().default("false"),
|
||||
AUTH_SYNC_OIDC_ROLES_ON_LOGIN: z.enum(["true", "false"]).default("true"),
|
||||
AUTH_ROLE_MAP: z.string().optional(),
|
||||
AUTH_DEFAULT_ROLE: z.string().optional().default("pending"),
|
||||
AUTH_ALLOW_LINKING: z.enum(["true", "false"]).default("true"),
|
||||
AUTH_ALLOW_UNLINKING: z.enum(["true", "false"]).default("true"),
|
||||
|
||||
AUTH_SYNC_OIDC_ROLES_ON_LOGIN: z.enum(["true", "false"]).default("true"),
|
||||
AUTH_ROLE_MAP: z.string().optional(),
|
||||
AUTH_DEFAULT_ROLE: z.string().optional().default("pending"),
|
||||
AUTH_ALLOW_LINKING: z.enum(["true", "false"]).default("true"),
|
||||
AUTH_ALLOW_UNLINKING: z.enum(["true", "false"]).default("true"),
|
||||
PRIVATE_PATH: z.string().optional(),
|
||||
},
|
||||
client: {
|
||||
NEXT_PUBLIC_PROJECT_VERSION: z.string().optional(),
|
||||
},
|
||||
runtimeEnv: {
|
||||
NEXT_PUBLIC_PROJECT_VERSION: version || "Unknown Version",
|
||||
LOG_LEVEL: process.env.LOG_LEVEL,
|
||||
|
||||
PRIVATE_PATH: z.string().optional(),
|
||||
},
|
||||
client: {
|
||||
NEXT_PUBLIC_PROJECT_VERSION: z.string().optional(),
|
||||
},
|
||||
runtimeEnv: {
|
||||
NEXT_PUBLIC_PROJECT_VERSION: version || "Unknown Version",
|
||||
LOG_LEVEL: process.env.LOG_LEVEL,
|
||||
PROJECT_NAME: process.env.PROJECT_NAME,
|
||||
PROJECT_DESCRIPTION: process.env.PROJECT_DESCRIPTION,
|
||||
PROJECT_URL: process.env.PROJECT_URL,
|
||||
PROJECT_SECRET: process.env.PROJECT_SECRET,
|
||||
|
||||
PROJECT_NAME: process.env.PROJECT_NAME,
|
||||
PROJECT_DESCRIPTION: process.env.PROJECT_DESCRIPTION,
|
||||
PROJECT_URL: process.env.PROJECT_URL,
|
||||
PROJECT_SECRET: process.env.PROJECT_SECRET,
|
||||
DATABASE_URL: process.env.DATABASE_URL,
|
||||
|
||||
DATABASE_URL: process.env.DATABASE_URL,
|
||||
TRUSTED_DOMAINS: process.env.TRUSTED_DOMAINS,
|
||||
|
||||
TRUSTED_DOMAINS: process.env.TRUSTED_DOMAINS,
|
||||
SMTP_PASSWORD: process.env.SMTP_PASSWORD,
|
||||
SMTP_FROM: process.env.SMTP_FROM,
|
||||
SMTP_HOST: process.env.SMTP_HOST,
|
||||
SMTP_PORT: process.env.SMTP_PORT,
|
||||
SMTP_USER: process.env.SMTP_USER,
|
||||
SMTP_SECURE: process.env.SMTP_SECURE,
|
||||
|
||||
SMTP_PASSWORD: process.env.SMTP_PASSWORD,
|
||||
SMTP_FROM: process.env.SMTP_FROM,
|
||||
SMTP_HOST: process.env.SMTP_HOST,
|
||||
SMTP_PORT: process.env.SMTP_PORT,
|
||||
SMTP_USER: process.env.SMTP_USER,
|
||||
SMTP_SECURE: process.env.SMTP_SECURE,
|
||||
RETENTION_CRON: process.env.RETENTION_CRON,
|
||||
CLEANING_HEALTHCHECK_LOGS_CRON: process.env.CLEANING_HEALTHCHECK_LOGS_CRON,
|
||||
|
||||
RETENTION_CRON: process.env.RETENTION_CRON,
|
||||
CLEANING_HEALTHCHECK_LOGS_CRON: process.env.CLEANING_HEALTHCHECK_LOGS_CRON,
|
||||
AUTH_OIDC_ID: process.env.AUTH_OIDC_ID,
|
||||
AUTH_OIDC_TITLE: process.env.AUTH_OIDC_TITLE,
|
||||
AUTH_OIDC_DESC: process.env.AUTH_OIDC_DESC,
|
||||
AUTH_OIDC_ICON: process.env.AUTH_OIDC_ICON,
|
||||
AUTH_OIDC_CLIENT: process.env.AUTH_OIDC_CLIENT,
|
||||
AUTH_OIDC_SECRET: process.env.AUTH_OIDC_SECRET,
|
||||
AUTH_OIDC_ISSUER_URL: process.env.AUTH_OIDC_ISSUER_URL,
|
||||
AUTH_OIDC_HOST: process.env.AUTH_OIDC_HOST,
|
||||
AUTH_OIDC_SCOPES: process.env.AUTH_OIDC_SCOPES,
|
||||
AUTH_OIDC_DISCOVERY_ENDPOINT: process.env.AUTH_OIDC_DISCOVERY_ENDPOINT,
|
||||
AUTH_OIDC_JWKS_ENDPOINT: process.env.AUTH_OIDC_JWKS_ENDPOINT,
|
||||
AUTH_OIDC_PKCE: process.env.AUTH_OIDC_PKCE,
|
||||
|
||||
AUTH_OIDC_ID: process.env.AUTH_OIDC_ID,
|
||||
AUTH_OIDC_TITLE: process.env.AUTH_OIDC_TITLE,
|
||||
AUTH_OIDC_DESC: process.env.AUTH_OIDC_DESC,
|
||||
AUTH_OIDC_ICON: process.env.AUTH_OIDC_ICON,
|
||||
AUTH_OIDC_CLIENT: process.env.AUTH_OIDC_CLIENT,
|
||||
AUTH_OIDC_SECRET: process.env.AUTH_OIDC_SECRET,
|
||||
AUTH_OIDC_ISSUER_URL: process.env.AUTH_OIDC_ISSUER_URL,
|
||||
AUTH_OIDC_HOST: process.env.AUTH_OIDC_HOST,
|
||||
AUTH_OIDC_SCOPES: process.env.AUTH_OIDC_SCOPES,
|
||||
AUTH_OIDC_DISCOVERY_ENDPOINT: process.env.AUTH_OIDC_DISCOVERY_ENDPOINT,
|
||||
AUTH_OIDC_JWKS_ENDPOINT: process.env.AUTH_OIDC_JWKS_ENDPOINT,
|
||||
AUTH_OIDC_PKCE: process.env.AUTH_OIDC_PKCE,
|
||||
AUTH_GOOGLE_ID: process.env.AUTH_GOOGLE_ID,
|
||||
AUTH_GOOGLE_SECRET: process.env.AUTH_GOOGLE_SECRET,
|
||||
|
||||
AUTH_GOOGLE_ID: process.env.AUTH_GOOGLE_ID,
|
||||
AUTH_GOOGLE_SECRET: process.env.AUTH_GOOGLE_SECRET,
|
||||
AUTH_GITHUB_ID: process.env.AUTH_GITHUB_ID,
|
||||
AUTH_GITHUB_SECRET: process.env.AUTH_GITHUB_SECRET,
|
||||
|
||||
AUTH_GITHUB_ID: process.env.AUTH_GITHUB_ID,
|
||||
AUTH_GITHUB_SECRET: process.env.AUTH_GITHUB_SECRET,
|
||||
AUTH_SOCIAL_ID: process.env.AUTH_SOCIAL_ID,
|
||||
AUTH_SOCIAL_TITLE: process.env.AUTH_SOCIAL_TITLE,
|
||||
AUTH_SOCIAL_DESC: process.env.AUTH_SOCIAL_DESC,
|
||||
AUTH_SOCIAL_ICON: process.env.AUTH_SOCIAL_ICON,
|
||||
AUTH_SOCIAL_CLIENT: process.env.AUTH_SOCIAL_CLIENT,
|
||||
AUTH_SOCIAL_SECRET: process.env.AUTH_SOCIAL_SECRET,
|
||||
|
||||
AUTH_SOCIAL_ID: process.env.AUTH_SOCIAL_ID,
|
||||
AUTH_SOCIAL_TITLE: process.env.AUTH_SOCIAL_TITLE,
|
||||
AUTH_SOCIAL_DESC: process.env.AUTH_SOCIAL_DESC,
|
||||
AUTH_SOCIAL_ICON: process.env.AUTH_SOCIAL_ICON,
|
||||
AUTH_SOCIAL_CLIENT: process.env.AUTH_SOCIAL_CLIENT,
|
||||
AUTH_SOCIAL_SECRET: process.env.AUTH_SOCIAL_SECRET,
|
||||
ALLOWED_GROUP: process.env.ALLOWED_GROUP,
|
||||
|
||||
ALLOWED_GROUP: process.env.ALLOWED_GROUP,
|
||||
AUTH_EMAIL_PASSWORD_ENABLED: process.env.AUTH_EMAIL_PASSWORD_ENABLED,
|
||||
AUTH_SIGNUP_ENABLED: process.env.AUTH_SIGNUP_ENABLED,
|
||||
AUTH_PASSKEY_ENABLED: process.env.AUTH_PASSKEY_ENABLED,
|
||||
|
||||
AUTH_EMAIL_PASSWORD_ENABLED: process.env.AUTH_EMAIL_PASSWORD_ENABLED,
|
||||
AUTH_SIGNUP_ENABLED: process.env.AUTH_SIGNUP_ENABLED,
|
||||
AUTH_PASSKEY_ENABLED: process.env.AUTH_PASSKEY_ENABLED,
|
||||
AUTH_SYNC_OIDC_ROLES_ON_LOGIN: process.env.AUTH_SYNC_OIDC_ROLES_ON_LOGIN,
|
||||
|
||||
AUTH_SYNC_OIDC_ROLES_ON_LOGIN: process.env.AUTH_SYNC_OIDC_ROLES_ON_LOGIN,
|
||||
AUTH_ROLE_MAP: process.env.AUTH_ROLE_MAP,
|
||||
|
||||
AUTH_ROLE_MAP: process.env.AUTH_ROLE_MAP,
|
||||
AUTH_ALLOW_LINKING: process.env.AUTH_ALLOW_LINKING,
|
||||
AUTH_ALLOW_UNLINKING: process.env.AUTH_ALLOW_UNLINKING,
|
||||
|
||||
AUTH_ALLOW_LINKING: process.env.AUTH_ALLOW_LINKING,
|
||||
AUTH_ALLOW_UNLINKING: process.env.AUTH_ALLOW_UNLINKING,
|
||||
PRIVATE_PATH:
|
||||
process.env.PRIVATE_PATH || path.join(process.cwd(), "private"),
|
||||
|
||||
PRIVATE_PATH:
|
||||
process.env.PRIVATE_PATH || path.join(process.cwd(), "private"),
|
||||
},
|
||||
AUTH_DEFAULT_USER_NAME: process.env.AUTH_DEFAULT_USER_NAME,
|
||||
AUTH_DEFAULT_USER: process.env.AUTH_DEFAULT_USER,
|
||||
AUTH_DEFAULT_PASSWORD: process.env.AUTH_DEFAULT_PASSWORD,
|
||||
},
|
||||
});
|
||||
|
||||
+2
-2
@@ -1,11 +1,11 @@
|
||||
|
||||
export type SignUpUser = {
|
||||
name: string
|
||||
email: string
|
||||
password?: string
|
||||
password: string
|
||||
callbackURL?: string
|
||||
role?: string
|
||||
theme: string
|
||||
emailVerified?: boolean
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
import { env } from "@/env.mjs";
|
||||
import { db, makeMigration } from "@/db";
|
||||
import { eq } from "drizzle-orm";
|
||||
import * as drizzleDb from "@/db";
|
||||
import {cleaningHealthcheckLogsJob, cleaningJob, healthcheckAgentAndDatabaseJob, retentionJob} from "@/lib/tasks";
|
||||
import { generateRSAKeys, getOrCreateMasterKey } from "@/utils/rsa-keys";
|
||||
import { StorageProviderKind } from "@/features/storages/types";
|
||||
import {logger} from "@/lib/logger";
|
||||
import {withUpdatedAt} from "@/db/utils";
|
||||
|
||||
const log = logger.child({module: "init"});
|
||||
|
||||
export async function init() {
|
||||
consoleAscii();
|
||||
|
||||
log.info("====Init Functions====");
|
||||
await getOrCreateMasterKey();
|
||||
await generateRSAKeys();
|
||||
await makeMigration();
|
||||
await createDefaultOrganization();
|
||||
await createSettingsIfNotExist();
|
||||
log.info("====Initialization completed====");
|
||||
await setupCronJobs();
|
||||
|
||||
if (
|
||||
(env.AUTH_GOOGLE_ID && env.AUTH_GOOGLE_SECRET) ||
|
||||
(env.AUTH_GITHUB_ID && env.AUTH_GITHUB_SECRET)
|
||||
) {
|
||||
log.warn(
|
||||
{
|
||||
deprecated: true,
|
||||
provider: "oauth_env",
|
||||
message: "You have set up OAuth credentials in your environment variables, but the format is now different. Please update your environment variables to use the new format. For example, if you were using AUTH_GOOGLE_ID and AUTH_GOOGLE_SECRET, you should now use AUTH_SOCIAL_GOOGLE_CLIENT and AUTH_SOCIAL_GOOGLE_SECRET. Please refer to the documentation for more details. (https://portabase.io/docs/dashboard/auth/oauth2/setup#dynamic-providers)"
|
||||
},
|
||||
"Deprecated OAuth environment variables detected",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function setupCronJobs() {
|
||||
|
||||
log.info("==== Setting up Cron Jobs ====");
|
||||
retentionJob.start();
|
||||
cleaningJob.start();
|
||||
cleaningHealthcheckLogsJob.start();
|
||||
healthcheckAgentAndDatabaseJob.start();
|
||||
log.info("==== Cron jobs started ====");
|
||||
}
|
||||
|
||||
async function createSettingsIfNotExist() {
|
||||
await db.transaction(async (tx) => {
|
||||
|
||||
const systemSettingsValues = {
|
||||
name: "system",
|
||||
smtpPassword: env.SMTP_PASSWORD ?? null,
|
||||
smtpFrom: env.SMTP_FROM ?? null,
|
||||
smtpHost: env.SMTP_HOST ?? null,
|
||||
smtpPort: env.SMTP_PORT ?? null,
|
||||
smtpUser: env.SMTP_USER ?? null,
|
||||
smtpSecure: env.SMTP_SECURE ?? false,
|
||||
};
|
||||
|
||||
const [systemSetting] = await tx
|
||||
.select()
|
||||
.from(drizzleDb.schemas.setting)
|
||||
.where(eq(drizzleDb.schemas.setting.name, "system"))
|
||||
.limit(1);
|
||||
|
||||
const [finalSystemSetting] = systemSetting
|
||||
? await tx
|
||||
.update(drizzleDb.schemas.setting)
|
||||
.set(systemSettingsValues)
|
||||
.where(eq(drizzleDb.schemas.setting.name, "system"))
|
||||
.returning()
|
||||
: await tx
|
||||
.insert(drizzleDb.schemas.setting)
|
||||
.values(systemSettingsValues)
|
||||
.returning();
|
||||
|
||||
const localStorageValues = {
|
||||
provider: "local" as StorageProviderKind,
|
||||
enabled: true,
|
||||
name: "System",
|
||||
config: {},
|
||||
};
|
||||
|
||||
const [existingLocalStorage] = await tx
|
||||
.select()
|
||||
.from(drizzleDb.schemas.storageChannel)
|
||||
.where(eq(drizzleDb.schemas.storageChannel.provider, "local"))
|
||||
.limit(1);
|
||||
|
||||
const [localStorage] = existingLocalStorage
|
||||
? await tx
|
||||
.update(drizzleDb.schemas.storageChannel)
|
||||
.set(localStorageValues)
|
||||
.where(eq(drizzleDb.schemas.storageChannel.provider, "local"))
|
||||
.returning()
|
||||
: await tx
|
||||
.insert(drizzleDb.schemas.storageChannel)
|
||||
.values(localStorageValues)
|
||||
.returning();
|
||||
|
||||
if (!finalSystemSetting.defaultStorageChannelId) {
|
||||
await tx
|
||||
.update(drizzleDb.schemas.setting)
|
||||
.set(withUpdatedAt({ defaultStorageChannelId: localStorage.id }))
|
||||
.where(eq(drizzleDb.schemas.setting.id, finalSystemSetting.id));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function createDefaultOrganization() {
|
||||
const defaultOrganizationConf = {
|
||||
slug: "default",
|
||||
name: "Default Organization",
|
||||
createdAt: new Date(),
|
||||
};
|
||||
|
||||
const [existing] = await db
|
||||
.select()
|
||||
.from(drizzleDb.schemas.organization)
|
||||
.where(eq(drizzleDb.schemas.organization.slug, "default"))
|
||||
.limit(1);
|
||||
|
||||
if (!existing) {
|
||||
log.info("==== Creating default Organization... ====");
|
||||
await db
|
||||
.insert(drizzleDb.schemas.organization)
|
||||
.values(defaultOrganizationConf);
|
||||
}
|
||||
}
|
||||
|
||||
function consoleAscii() {
|
||||
console.log(
|
||||
" \n" +
|
||||
" ____ __ __ \n" +
|
||||
" / __ \\____ _____/ /_____ _/ /_ ____ _________ \n" +
|
||||
" / /_/ / __ \\/ ___/ __/ __ / __ \\/ __ / ___/ _ \\ \n" +
|
||||
" / ____/ /_/ / / / /_/ /_/ / /_/ / /_/ (__ ) __/ \n" +
|
||||
" /_/ \\____/_/ \\__/\\__,_/_.___/\\__,_/____/\\___/ \n" +
|
||||
" \n" +
|
||||
` Community Edition v${env.NEXT_PUBLIC_PROJECT_VERSION} \n ` +
|
||||
" \n",
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import {cleaningHealthcheckLogsJob, cleaningJob, healthcheckAgentAndDatabaseJob, retentionJob} from "@/lib/tasks";
|
||||
import {logger} from "@/lib/logger";
|
||||
|
||||
const log = logger.child({module: "init/cron"});
|
||||
|
||||
|
||||
export async function setupCronJobs() {
|
||||
log.info("==== Setting up Cron Jobs ====");
|
||||
retentionJob.start();
|
||||
cleaningJob.start();
|
||||
cleaningHealthcheckLogsJob.start();
|
||||
healthcheckAgentAndDatabaseJob.start();
|
||||
log.info("==== Cron jobs started ====");
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { env } from "@/env.mjs";
|
||||
import { makeMigration } from "@/db";
|
||||
import { generateRSAKeys, getOrCreateMasterKey } from "@/utils/rsa-keys";
|
||||
import {logger} from "@/lib/logger";
|
||||
import {setupCronJobs} from "@/utils/init/cron";
|
||||
import {createSettingsIfNotExist} from "@/utils/init/setting";
|
||||
import {createDefaultOrganization} from "@/utils/init/organization";
|
||||
import {createDefaultUser} from "@/utils/init/user";
|
||||
|
||||
const log = logger.child({module: "init"});
|
||||
|
||||
export async function init() {
|
||||
consoleAscii();
|
||||
log.info("====Init Functions====");
|
||||
await getOrCreateMasterKey();
|
||||
await generateRSAKeys();
|
||||
await makeMigration();
|
||||
await createDefaultOrganization();
|
||||
await createSettingsIfNotExist();
|
||||
await createDefaultUser();
|
||||
log.info("====Initialization completed====");
|
||||
await setupCronJobs();
|
||||
if (
|
||||
(env.AUTH_GOOGLE_ID && env.AUTH_GOOGLE_SECRET) ||
|
||||
(env.AUTH_GITHUB_ID && env.AUTH_GITHUB_SECRET)
|
||||
) {
|
||||
log.warn(
|
||||
{
|
||||
deprecated: true,
|
||||
provider: "oauth_env",
|
||||
message: "You have set up OAuth credentials in your environment variables, but the format is now different. Please update your environment variables to use the new format. For example, if you were using AUTH_GOOGLE_ID and AUTH_GOOGLE_SECRET, you should now use AUTH_SOCIAL_GOOGLE_CLIENT and AUTH_SOCIAL_GOOGLE_SECRET. Please refer to the documentation for more details. (https://portabase.io/docs/dashboard/auth/oauth2/setup#dynamic-providers)"
|
||||
},
|
||||
"Deprecated OAuth environment variables detected",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function consoleAscii() {
|
||||
console.log(
|
||||
" \n" +
|
||||
" ____ __ __ \n" +
|
||||
" / __ \\____ _____/ /_____ _/ /_ ____ _________ \n" +
|
||||
" / /_/ / __ \\/ ___/ __/ __ / __ \\/ __ / ___/ _ \\ \n" +
|
||||
" / ____/ /_/ / / / /_/ /_/ / /_/ / /_/ (__ ) __/ \n" +
|
||||
" /_/ \\____/_/ \\__/\\__,_/_.___/\\__,_/____/\\___/ \n" +
|
||||
" \n" +
|
||||
` Community Edition v${env.NEXT_PUBLIC_PROJECT_VERSION} \n ` +
|
||||
" \n",
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import {db} from "@/db";
|
||||
import * as drizzleDb from "@/db";
|
||||
import {eq} from "drizzle-orm";
|
||||
import {logger} from "@/lib/logger";
|
||||
|
||||
const log = logger.child({module: "init/organization"});
|
||||
|
||||
export async function createDefaultOrganization() {
|
||||
const defaultOrganizationConf = {
|
||||
slug: "default",
|
||||
name: "Default Organization",
|
||||
createdAt: new Date(),
|
||||
};
|
||||
|
||||
const [existing] = await db
|
||||
.select()
|
||||
.from(drizzleDb.schemas.organization)
|
||||
.where(eq(drizzleDb.schemas.organization.slug, "default"))
|
||||
.limit(1);
|
||||
|
||||
if (!existing) {
|
||||
log.info("==== Creating default Organization... ====");
|
||||
await db
|
||||
.insert(drizzleDb.schemas.organization)
|
||||
.values(defaultOrganizationConf);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import {db} from "@/db";
|
||||
import {env} from "@/env.mjs";
|
||||
import * as drizzleDb from "@/db";
|
||||
import {eq} from "drizzle-orm";
|
||||
import {StorageProviderKind} from "@/features/storages/types";
|
||||
import {withUpdatedAt} from "@/db/utils";
|
||||
|
||||
|
||||
export async function createSettingsIfNotExist() {
|
||||
await db.transaction(async (tx) => {
|
||||
|
||||
const systemSettingsValues = {
|
||||
name: "system",
|
||||
smtpPassword: env.SMTP_PASSWORD ?? null,
|
||||
smtpFrom: env.SMTP_FROM ?? null,
|
||||
smtpHost: env.SMTP_HOST ?? null,
|
||||
smtpPort: env.SMTP_PORT ?? null,
|
||||
smtpUser: env.SMTP_USER ?? null,
|
||||
smtpSecure: env.SMTP_SECURE ?? false,
|
||||
};
|
||||
|
||||
const [systemSetting] = await tx
|
||||
.select()
|
||||
.from(drizzleDb.schemas.setting)
|
||||
.where(eq(drizzleDb.schemas.setting.name, "system"))
|
||||
.limit(1);
|
||||
|
||||
const [finalSystemSetting] = systemSetting
|
||||
? await tx
|
||||
.update(drizzleDb.schemas.setting)
|
||||
.set(systemSettingsValues)
|
||||
.where(eq(drizzleDb.schemas.setting.name, "system"))
|
||||
.returning()
|
||||
: await tx
|
||||
.insert(drizzleDb.schemas.setting)
|
||||
.values(systemSettingsValues)
|
||||
.returning();
|
||||
|
||||
const localStorageValues = {
|
||||
provider: "local" as StorageProviderKind,
|
||||
enabled: true,
|
||||
name: "System",
|
||||
config: {},
|
||||
};
|
||||
|
||||
const [existingLocalStorage] = await tx
|
||||
.select()
|
||||
.from(drizzleDb.schemas.storageChannel)
|
||||
.where(eq(drizzleDb.schemas.storageChannel.provider, "local"))
|
||||
.limit(1);
|
||||
|
||||
const [localStorage] = existingLocalStorage
|
||||
? await tx
|
||||
.update(drizzleDb.schemas.storageChannel)
|
||||
.set(localStorageValues)
|
||||
.where(eq(drizzleDb.schemas.storageChannel.provider, "local"))
|
||||
.returning()
|
||||
: await tx
|
||||
.insert(drizzleDb.schemas.storageChannel)
|
||||
.values(localStorageValues)
|
||||
.returning();
|
||||
|
||||
if (!finalSystemSetting.defaultStorageChannelId) {
|
||||
await tx
|
||||
.update(drizzleDb.schemas.setting)
|
||||
.set(withUpdatedAt({ defaultStorageChannelId: localStorage.id }))
|
||||
.where(eq(drizzleDb.schemas.setting.id, finalSystemSetting.id));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import {db} from "@/db";
|
||||
import * as drizzleDb from "@/db";
|
||||
import {count, eq} from "drizzle-orm";
|
||||
import {logger} from "@/lib/logger";
|
||||
import type { SignUpUser } from "@/types/auth";
|
||||
import {env} from "@/env.mjs";
|
||||
import {createUserDb} from "@/db/services/user";
|
||||
|
||||
const log = logger.child({module: "init/user"});
|
||||
|
||||
export async function createDefaultUser() {
|
||||
|
||||
const hasRequiredEnv =
|
||||
env.AUTH_DEFAULT_USER &&
|
||||
env.AUTH_DEFAULT_PASSWORD &&
|
||||
env.AUTH_DEFAULT_USER_NAME;
|
||||
|
||||
if (!hasRequiredEnv) {
|
||||
log.info(
|
||||
"Default admin creation skipped: missing environment variables.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("Checking default super admin...");
|
||||
|
||||
const existingSuperAdmin = await db.query.user.findFirst({
|
||||
where: eq(drizzleDb.schemas.user.role, "superadmin"),
|
||||
});
|
||||
|
||||
if (existingSuperAdmin) {
|
||||
log.info(
|
||||
`CreateDefaultUser skipped: a superadmin already exists (${existingSuperAdmin.email}).`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const userData: SignUpUser = {
|
||||
name: env.AUTH_DEFAULT_USER_NAME!,
|
||||
email: env.AUTH_DEFAULT_USER!,
|
||||
password: env.AUTH_DEFAULT_PASSWORD!,
|
||||
theme: "system",
|
||||
role: "superadmin",
|
||||
emailVerified: true,
|
||||
};
|
||||
|
||||
|
||||
const newUser = await createUserDb(userData);
|
||||
|
||||
if (newUser) {
|
||||
log.info(`Default super admin created (${userData.email}).`);
|
||||
|
||||
const defaultOrgSlug = "default";
|
||||
const defaultOrg = await db.query.organization.findFirst({
|
||||
where: eq(drizzleDb.schemas.organization.slug, defaultOrgSlug),
|
||||
});
|
||||
|
||||
if (defaultOrg) {
|
||||
await db.insert(drizzleDb.schemas.member).values({
|
||||
userId: newUser.id,
|
||||
organizationId: defaultOrg.id,
|
||||
role: "owner",
|
||||
});
|
||||
} else {
|
||||
log.warn(
|
||||
"Default organization not found. Cannot assign member.",
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -24,4 +24,61 @@ export function generateValidPassword(length = 12) {
|
||||
}
|
||||
|
||||
return passwordChars.join('');
|
||||
}
|
||||
|
||||
|
||||
export interface PasswordValidationResult {
|
||||
valid: boolean;
|
||||
errors: string[];
|
||||
score: number;
|
||||
}
|
||||
|
||||
const PASSWORD_RULES = [
|
||||
{
|
||||
key: "minLength",
|
||||
regex: /.{8,}/,
|
||||
message: "Password must contain at least 8 characters",
|
||||
},
|
||||
{
|
||||
key: "number",
|
||||
regex: /[0-9]/,
|
||||
message: "Password must contain at least 1 number",
|
||||
},
|
||||
{
|
||||
key: "lowercase",
|
||||
regex: /[a-z]/,
|
||||
message: "Password must contain at least 1 lowercase letter",
|
||||
},
|
||||
{
|
||||
key: "uppercase",
|
||||
regex: /[A-Z]/,
|
||||
message: "Password must contain at least 1 uppercase letter",
|
||||
},
|
||||
{
|
||||
key: "specialChar",
|
||||
regex: /[^a-zA-Z0-9]/,
|
||||
message: "Password must contain at least 1 special character",
|
||||
},
|
||||
];
|
||||
|
||||
export function validatePassword(
|
||||
password: string,
|
||||
): PasswordValidationResult {
|
||||
const failedRules = PASSWORD_RULES.filter(
|
||||
(rule) => !rule.regex.test(password),
|
||||
);
|
||||
|
||||
return {
|
||||
valid: failedRules.length === 0,
|
||||
errors: failedRules.map((r) => r.message),
|
||||
score: PASSWORD_RULES.length - failedRules.length,
|
||||
};
|
||||
}
|
||||
|
||||
export function assertValidPassword(password: string) {
|
||||
const result = validatePassword(password);
|
||||
|
||||
if (!result.valid) {
|
||||
throw new Error(result.errors.join(", "));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user