mirror of
https://github.com/RayLabsHQ/gitea-mirror.git
synced 2025-12-06 03:26:44 +03:00
Fixing issues with Better Auth
This commit is contained in:
@@ -10,9 +10,8 @@ PORT=4321
|
||||
DATABASE_URL=sqlite://data/gitea-mirror.db
|
||||
|
||||
# Security
|
||||
JWT_SECRET=change-this-to-a-secure-random-string-in-production
|
||||
BETTER_AUTH_SECRET=change-this-to-a-secure-random-string-in-production
|
||||
BETTER_AUTH_URL=http://localhost:3000
|
||||
BETTER_AUTH_URL=http://localhost:4321
|
||||
|
||||
# Optional GitHub/Gitea Mirror Configuration (for docker-compose, can also be set via web UI)
|
||||
# Uncomment and set as needed. These are passed as environment variables to the container.
|
||||
|
||||
@@ -15,7 +15,7 @@ services:
|
||||
- DATABASE_URL=file:data/gitea-mirror.db
|
||||
- HOST=0.0.0.0
|
||||
- PORT=4321
|
||||
- JWT_SECRET=${JWT_SECRET:-your-secret-key-change-this-in-production}
|
||||
- BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET:-your-secret-key-change-this-in-production}
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--no-verbose", "--tries=3", "--spider", "http://localhost:4321/api/health"]
|
||||
interval: 30s
|
||||
|
||||
@@ -66,7 +66,7 @@ services:
|
||||
- DATABASE_URL=file:data/gitea-mirror.db
|
||||
- HOST=0.0.0.0
|
||||
- PORT=4321
|
||||
- JWT_SECRET=dev-secret-key
|
||||
- BETTER_AUTH_SECRET=dev-secret-key
|
||||
# GitHub/Gitea Mirror Config
|
||||
- GITHUB_USERNAME=${GITHUB_USERNAME:-your-github-username}
|
||||
- GITHUB_TOKEN=${GITHUB_TOKEN:-your-github-token}
|
||||
|
||||
@@ -28,7 +28,7 @@ services:
|
||||
- DATABASE_URL=file:data/gitea-mirror.db
|
||||
- HOST=0.0.0.0
|
||||
- PORT=4321
|
||||
- JWT_SECRET=${JWT_SECRET:-your-secret-key-change-this-in-production}
|
||||
- BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET:-your-secret-key-change-this-in-production}
|
||||
# GitHub/Gitea Mirror Config
|
||||
- GITHUB_USERNAME=${GITHUB_USERNAME:-}
|
||||
- GITHUB_TOKEN=${GITHUB_TOKEN:-}
|
||||
|
||||
@@ -52,15 +52,26 @@ if [ "$GITEA_SKIP_TLS_VERIFY" = "true" ]; then
|
||||
export NODE_TLS_REJECT_UNAUTHORIZED=0
|
||||
fi
|
||||
|
||||
# Generate a secure JWT secret if one isn't provided or is using the default value
|
||||
JWT_SECRET_FILE="/app/data/.jwt_secret"
|
||||
if [ "$JWT_SECRET" = "your-secret-key-change-this-in-production" ] || [ -z "$JWT_SECRET" ]; then
|
||||
# Generate a secure BETTER_AUTH_SECRET if one isn't provided or is using the default value
|
||||
BETTER_AUTH_SECRET_FILE="/app/data/.better_auth_secret"
|
||||
JWT_SECRET_FILE="/app/data/.jwt_secret" # Old file for backward compatibility
|
||||
|
||||
if [ "$BETTER_AUTH_SECRET" = "your-secret-key-change-this-in-production" ] || [ -z "$BETTER_AUTH_SECRET" ]; then
|
||||
# Check if we have a previously generated secret
|
||||
if [ -f "$JWT_SECRET_FILE" ]; then
|
||||
echo "Using previously generated JWT secret"
|
||||
export JWT_SECRET=$(cat "$JWT_SECRET_FILE")
|
||||
if [ -f "$BETTER_AUTH_SECRET_FILE" ]; then
|
||||
echo "Using previously generated BETTER_AUTH_SECRET"
|
||||
export BETTER_AUTH_SECRET=$(cat "$BETTER_AUTH_SECRET_FILE")
|
||||
# Check for old JWT_SECRET file for backward compatibility
|
||||
elif [ -f "$JWT_SECRET_FILE" ]; then
|
||||
echo "Migrating from old JWT_SECRET to BETTER_AUTH_SECRET"
|
||||
export BETTER_AUTH_SECRET=$(cat "$JWT_SECRET_FILE")
|
||||
# Save to new file
|
||||
echo "$BETTER_AUTH_SECRET" > "$BETTER_AUTH_SECRET_FILE"
|
||||
chmod 600 "$BETTER_AUTH_SECRET_FILE"
|
||||
# Optionally remove old file after successful migration
|
||||
rm -f "$JWT_SECRET_FILE"
|
||||
else
|
||||
echo "Generating a secure random JWT secret"
|
||||
echo "Generating a secure random BETTER_AUTH_SECRET"
|
||||
# Try to generate a secure random string using OpenSSL
|
||||
if command -v openssl >/dev/null 2>&1; then
|
||||
GENERATED_SECRET=$(openssl rand -hex 32)
|
||||
@@ -69,12 +80,12 @@ if [ "$JWT_SECRET" = "your-secret-key-change-this-in-production" ] || [ -z "$JWT
|
||||
echo "OpenSSL not found, using fallback method for random generation"
|
||||
GENERATED_SECRET=$(head -c 32 /dev/urandom | sha256sum | cut -d' ' -f1)
|
||||
fi
|
||||
export JWT_SECRET="$GENERATED_SECRET"
|
||||
export BETTER_AUTH_SECRET="$GENERATED_SECRET"
|
||||
# Save the secret to a file for persistence across container restarts
|
||||
echo "$GENERATED_SECRET" > "$JWT_SECRET_FILE"
|
||||
chmod 600 "$JWT_SECRET_FILE"
|
||||
echo "$GENERATED_SECRET" > "$BETTER_AUTH_SECRET_FILE"
|
||||
chmod 600 "$BETTER_AUTH_SECRET_FILE"
|
||||
fi
|
||||
echo "JWT_SECRET has been set to a secure random value"
|
||||
echo "BETTER_AUTH_SECRET has been set to a secure random value"
|
||||
fi
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
CREATE TABLE `accounts` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`account_id` text NOT NULL,
|
||||
`user_id` text NOT NULL,
|
||||
`provider_id` text NOT NULL,
|
||||
`provider_user_id` text,
|
||||
`access_token` text,
|
||||
`refresh_token` text,
|
||||
`expires_at` integer,
|
||||
`password` text,
|
||||
`created_at` integer DEFAULT (unixepoch()) NOT NULL,
|
||||
`updated_at` integer DEFAULT (unixepoch()) NOT NULL,
|
||||
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE INDEX `idx_accounts_account_id` ON `accounts` (`account_id`);--> statement-breakpoint
|
||||
CREATE INDEX `idx_accounts_user_id` ON `accounts` (`user_id`);--> statement-breakpoint
|
||||
CREATE INDEX `idx_accounts_provider` ON `accounts` (`provider_id`,`provider_user_id`);--> statement-breakpoint
|
||||
CREATE TABLE `configs` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`user_id` text NOT NULL,
|
||||
@@ -120,11 +138,43 @@ CREATE INDEX `idx_repositories_owner` ON `repositories` (`owner`);--> statement-
|
||||
CREATE INDEX `idx_repositories_organization` ON `repositories` (`organization`);--> statement-breakpoint
|
||||
CREATE INDEX `idx_repositories_is_fork` ON `repositories` (`is_fork`);--> statement-breakpoint
|
||||
CREATE INDEX `idx_repositories_is_starred` ON `repositories` (`is_starred`);--> statement-breakpoint
|
||||
CREATE TABLE `sessions` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`token` text NOT NULL,
|
||||
`user_id` text NOT NULL,
|
||||
`expires_at` integer NOT NULL,
|
||||
`ip_address` text,
|
||||
`user_agent` text,
|
||||
`created_at` integer DEFAULT (unixepoch()) NOT NULL,
|
||||
`updated_at` integer DEFAULT (unixepoch()) NOT NULL,
|
||||
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `sessions_token_unique` ON `sessions` (`token`);--> statement-breakpoint
|
||||
CREATE INDEX `idx_sessions_user_id` ON `sessions` (`user_id`);--> statement-breakpoint
|
||||
CREATE INDEX `idx_sessions_token` ON `sessions` (`token`);--> statement-breakpoint
|
||||
CREATE INDEX `idx_sessions_expires_at` ON `sessions` (`expires_at`);--> statement-breakpoint
|
||||
CREATE TABLE `users` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`username` text NOT NULL,
|
||||
`password` text NOT NULL,
|
||||
`name` text,
|
||||
`email` text NOT NULL,
|
||||
`email_verified` integer DEFAULT false NOT NULL,
|
||||
`image` text,
|
||||
`created_at` integer DEFAULT (unixepoch()) NOT NULL,
|
||||
`updated_at` integer DEFAULT (unixepoch()) NOT NULL
|
||||
`updated_at` integer DEFAULT (unixepoch()) NOT NULL,
|
||||
`username` text
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `users_email_unique` ON `users` (`email`);--> statement-breakpoint
|
||||
CREATE TABLE `verification_tokens` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`token` text NOT NULL,
|
||||
`identifier` text NOT NULL,
|
||||
`type` text NOT NULL,
|
||||
`expires_at` integer NOT NULL,
|
||||
`created_at` integer DEFAULT (unixepoch()) NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `verification_tokens_token_unique` ON `verification_tokens` (`token`);--> statement-breakpoint
|
||||
CREATE INDEX `idx_verification_tokens_token` ON `verification_tokens` (`token`);--> statement-breakpoint
|
||||
CREATE INDEX `idx_verification_tokens_identifier` ON `verification_tokens` (`identifier`);
|
||||
@@ -1,45 +0,0 @@
|
||||
CREATE TABLE `accounts` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`user_id` text NOT NULL,
|
||||
`provider_id` text NOT NULL,
|
||||
`provider_user_id` text NOT NULL,
|
||||
`access_token` text,
|
||||
`refresh_token` text,
|
||||
`expires_at` integer,
|
||||
`password` text,
|
||||
`created_at` integer DEFAULT (unixepoch()) NOT NULL,
|
||||
`updated_at` integer DEFAULT (unixepoch()) NOT NULL,
|
||||
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE INDEX `idx_accounts_user_id` ON `accounts` (`user_id`);--> statement-breakpoint
|
||||
CREATE INDEX `idx_accounts_provider` ON `accounts` (`provider_id`,`provider_user_id`);--> statement-breakpoint
|
||||
CREATE TABLE `sessions` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`token` text NOT NULL,
|
||||
`user_id` text NOT NULL,
|
||||
`expires_at` integer NOT NULL,
|
||||
`ip_address` text,
|
||||
`user_agent` text,
|
||||
`created_at` integer DEFAULT (unixepoch()) NOT NULL,
|
||||
`updated_at` integer DEFAULT (unixepoch()) NOT NULL,
|
||||
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `sessions_token_unique` ON `sessions` (`token`);--> statement-breakpoint
|
||||
CREATE INDEX `idx_sessions_user_id` ON `sessions` (`user_id`);--> statement-breakpoint
|
||||
CREATE INDEX `idx_sessions_token` ON `sessions` (`token`);--> statement-breakpoint
|
||||
CREATE INDEX `idx_sessions_expires_at` ON `sessions` (`expires_at`);--> statement-breakpoint
|
||||
CREATE TABLE `verification_tokens` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`token` text NOT NULL,
|
||||
`identifier` text NOT NULL,
|
||||
`type` text NOT NULL,
|
||||
`expires_at` integer NOT NULL,
|
||||
`created_at` integer DEFAULT (unixepoch()) NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `verification_tokens_token_unique` ON `verification_tokens` (`token`);--> statement-breakpoint
|
||||
CREATE INDEX `idx_verification_tokens_token` ON `verification_tokens` (`token`);--> statement-breakpoint
|
||||
CREATE INDEX `idx_verification_tokens_identifier` ON `verification_tokens` (`identifier`);--> statement-breakpoint
|
||||
ALTER TABLE `users` ADD `email_verified` integer DEFAULT false NOT NULL;
|
||||
@@ -1,9 +1,135 @@
|
||||
{
|
||||
"version": "6",
|
||||
"dialect": "sqlite",
|
||||
"id": "b963d828-412d-4192-b0aa-3b13b83cfba8",
|
||||
"id": "7782b8ba-bdae-42e8-b8a7-614f8be30a58",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"tables": {
|
||||
"accounts": {
|
||||
"name": "accounts",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"account_id": {
|
||||
"name": "account_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"provider_id": {
|
||||
"name": "provider_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"provider_user_id": {
|
||||
"name": "provider_user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"access_token": {
|
||||
"name": "access_token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"refresh_token": {
|
||||
"name": "refresh_token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"expires_at": {
|
||||
"name": "expires_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"password": {
|
||||
"name": "password",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(unixepoch())"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(unixepoch())"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"idx_accounts_account_id": {
|
||||
"name": "idx_accounts_account_id",
|
||||
"columns": [
|
||||
"account_id"
|
||||
],
|
||||
"isUnique": false
|
||||
},
|
||||
"idx_accounts_user_id": {
|
||||
"name": "idx_accounts_user_id",
|
||||
"columns": [
|
||||
"user_id"
|
||||
],
|
||||
"isUnique": false
|
||||
},
|
||||
"idx_accounts_provider": {
|
||||
"name": "idx_accounts_provider",
|
||||
"columns": [
|
||||
"provider_id",
|
||||
"provider_user_id"
|
||||
],
|
||||
"isUnique": false
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"accounts_user_id_users_id_fk": {
|
||||
"name": "accounts_user_id_users_id_fk",
|
||||
"tableFrom": "accounts",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"configs": {
|
||||
"name": "configs",
|
||||
"columns": {
|
||||
@@ -887,8 +1013,8 @@
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"users": {
|
||||
"name": "users",
|
||||
"sessions": {
|
||||
"name": "sessions",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
@@ -897,27 +1023,41 @@
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"username": {
|
||||
"name": "username",
|
||||
"token": {
|
||||
"name": "token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"password": {
|
||||
"name": "password",
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
"expires_at": {
|
||||
"name": "expires_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"ip_address": {
|
||||
"name": "ip_address",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"user_agent": {
|
||||
"name": "user_agent",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "integer",
|
||||
@@ -935,7 +1075,202 @@
|
||||
"default": "(unixepoch())"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"indexes": {
|
||||
"sessions_token_unique": {
|
||||
"name": "sessions_token_unique",
|
||||
"columns": [
|
||||
"token"
|
||||
],
|
||||
"isUnique": true
|
||||
},
|
||||
"idx_sessions_user_id": {
|
||||
"name": "idx_sessions_user_id",
|
||||
"columns": [
|
||||
"user_id"
|
||||
],
|
||||
"isUnique": false
|
||||
},
|
||||
"idx_sessions_token": {
|
||||
"name": "idx_sessions_token",
|
||||
"columns": [
|
||||
"token"
|
||||
],
|
||||
"isUnique": false
|
||||
},
|
||||
"idx_sessions_expires_at": {
|
||||
"name": "idx_sessions_expires_at",
|
||||
"columns": [
|
||||
"expires_at"
|
||||
],
|
||||
"isUnique": false
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"sessions_user_id_users_id_fk": {
|
||||
"name": "sessions_user_id_users_id_fk",
|
||||
"tableFrom": "sessions",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"users": {
|
||||
"name": "users",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"email_verified": {
|
||||
"name": "email_verified",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": false
|
||||
},
|
||||
"image": {
|
||||
"name": "image",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(unixepoch())"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(unixepoch())"
|
||||
},
|
||||
"username": {
|
||||
"name": "username",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"users_email_unique": {
|
||||
"name": "users_email_unique",
|
||||
"columns": [
|
||||
"email"
|
||||
],
|
||||
"isUnique": true
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"verification_tokens": {
|
||||
"name": "verification_tokens",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"token": {
|
||||
"name": "token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"identifier": {
|
||||
"name": "identifier",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"type": {
|
||||
"name": "type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"expires_at": {
|
||||
"name": "expires_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(unixepoch())"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"verification_tokens_token_unique": {
|
||||
"name": "verification_tokens_token_unique",
|
||||
"columns": [
|
||||
"token"
|
||||
],
|
||||
"isUnique": true
|
||||
},
|
||||
"idx_verification_tokens_token": {
|
||||
"name": "idx_verification_tokens_token",
|
||||
"columns": [
|
||||
"token"
|
||||
],
|
||||
"isUnique": false
|
||||
},
|
||||
"idx_verification_tokens_identifier": {
|
||||
"name": "idx_verification_tokens_identifier",
|
||||
"columns": [
|
||||
"identifier"
|
||||
],
|
||||
"isUnique": false
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,15 +5,8 @@
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "6",
|
||||
"when": 1752161775910,
|
||||
"tag": "0000_big_xorn",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 1,
|
||||
"version": "6",
|
||||
"when": 1752166860985,
|
||||
"tag": "0001_vengeful_whirlwind",
|
||||
"when": 1752171873627,
|
||||
"tag": "0000_init",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
|
||||
@@ -7,7 +7,7 @@ CONTAINER="gitea-test"
|
||||
IMAGE="ubuntu:22.04"
|
||||
INSTALL_DIR="/opt/gitea-mirror"
|
||||
PORT=4321
|
||||
JWT_SECRET="$(openssl rand -hex 32)"
|
||||
BETTER_AUTH_SECRET="$(openssl rand -hex 32)"
|
||||
|
||||
BUN_ZIP="/tmp/bun-linux-x64.zip"
|
||||
BUN_URL="https://github.com/oven-sh/bun/releases/latest/download/bun-linux-x64.zip"
|
||||
@@ -73,7 +73,7 @@ Environment=NODE_ENV=production
|
||||
Environment=HOST=0.0.0.0
|
||||
Environment=PORT=$PORT
|
||||
Environment=DATABASE_URL=file:data/gitea-mirror.db
|
||||
Environment=JWT_SECRET=$JWT_SECRET
|
||||
Environment=BETTER_AUTH_SECRET=$BETTER_AUTH_SECRET
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
SERVICE
|
||||
|
||||
@@ -33,7 +33,7 @@ export function LoginForm() {
|
||||
toast.success('Login successful!');
|
||||
// Small delay before redirecting to see the success message
|
||||
setTimeout(() => {
|
||||
window.location.href = '/dashboard';
|
||||
window.location.href = '/';
|
||||
}, 1000);
|
||||
} catch (error) {
|
||||
showErrorToast(error, toast);
|
||||
|
||||
10
src/components/auth/LoginPage.tsx
Normal file
10
src/components/auth/LoginPage.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import { LoginForm } from './LoginForm';
|
||||
import Providers from '@/components/layout/Providers';
|
||||
|
||||
export function LoginPage() {
|
||||
return (
|
||||
<Providers>
|
||||
<LoginForm />
|
||||
</Providers>
|
||||
);
|
||||
}
|
||||
@@ -16,12 +16,11 @@ export function SignupForm() {
|
||||
setIsLoading(true);
|
||||
const form = e.currentTarget;
|
||||
const formData = new FormData(form);
|
||||
const username = formData.get('username') as string | null;
|
||||
const email = formData.get('email') as string | null;
|
||||
const password = formData.get('password') as string | null;
|
||||
const confirmPassword = formData.get('confirmPassword') as string | null;
|
||||
|
||||
if (!username || !email || !password || !confirmPassword) {
|
||||
if (!email || !password || !confirmPassword) {
|
||||
toast.error('Please fill in all fields');
|
||||
setIsLoading(false);
|
||||
return;
|
||||
@@ -34,11 +33,13 @@ export function SignupForm() {
|
||||
}
|
||||
|
||||
try {
|
||||
// Derive username from email (part before @)
|
||||
const username = email.split('@')[0];
|
||||
await register(username, email, password);
|
||||
toast.success('Account created successfully! Redirecting to dashboard...');
|
||||
// Small delay before redirecting to see the success message
|
||||
setTimeout(() => {
|
||||
window.location.href = '/dashboard';
|
||||
window.location.href = '/';
|
||||
}, 1500);
|
||||
} catch (error) {
|
||||
showErrorToast(error, toast);
|
||||
@@ -71,20 +72,6 @@ export function SignupForm() {
|
||||
<CardContent>
|
||||
<form id="signup-form" onSubmit={handleSignup}>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label htmlFor="username" className="block text-sm font-medium mb-1">
|
||||
Username
|
||||
</label>
|
||||
<input
|
||||
id="username"
|
||||
name="username"
|
||||
type="text"
|
||||
required
|
||||
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
placeholder="Enter your username"
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm font-medium mb-1">
|
||||
Email
|
||||
@@ -97,6 +84,7 @@ export function SignupForm() {
|
||||
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
placeholder="Enter your email"
|
||||
disabled={isLoading}
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
10
src/components/auth/SignupPage.tsx
Normal file
10
src/components/auth/SignupPage.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import { SignupForm } from './SignupForm';
|
||||
import Providers from '@/components/layout/Providers';
|
||||
|
||||
export function SignupPage() {
|
||||
return (
|
||||
<Providers>
|
||||
<SignupForm />
|
||||
</Providers>
|
||||
);
|
||||
}
|
||||
@@ -37,28 +37,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
const user = betterAuthSession.data?.user || null;
|
||||
const session = betterAuthSession.data || null;
|
||||
|
||||
// Check if this is first load and redirect if needed
|
||||
useEffect(() => {
|
||||
const checkFirstUser = async () => {
|
||||
if (!betterAuthSession.isPending && !user) {
|
||||
try {
|
||||
// Check if there are any users in the system
|
||||
const response = await fetch("/api/auth/check-users");
|
||||
if (response.status === 404) {
|
||||
// No users found, redirect to signup
|
||||
window.location.href = "/signup";
|
||||
} else if (!window.location.pathname.includes("/login")) {
|
||||
// User not authenticated, redirect to login
|
||||
window.location.href = "/login";
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to check users:", err);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
checkFirstUser();
|
||||
}, [betterAuthSession.isPending, user]);
|
||||
// Don't do any redirects here - let the pages handle their own redirect logic
|
||||
|
||||
const login = async (email: string, password: string) => {
|
||||
setIsLoading(true);
|
||||
@@ -67,7 +46,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
const result = await authClient.signIn.email({
|
||||
email,
|
||||
password,
|
||||
callbackURL: "/dashboard",
|
||||
callbackURL: "/",
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
@@ -93,9 +72,8 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
const result = await authClient.signUp.email({
|
||||
email,
|
||||
password,
|
||||
name: username, // Better Auth uses 'name' field
|
||||
username, // Also pass username as additional field
|
||||
callbackURL: "/dashboard",
|
||||
name: username, // Better Auth uses 'name' field for display name
|
||||
callbackURL: "/",
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
|
||||
@@ -3,13 +3,6 @@ import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
||||
import { sso, oidcProvider } from "better-auth/plugins";
|
||||
import type { BunSQLiteDatabase } from "drizzle-orm/bun-sqlite";
|
||||
|
||||
// Generate or use existing JWT secret
|
||||
const JWT_SECRET = process.env.JWT_SECRET || process.env.BETTER_AUTH_SECRET;
|
||||
|
||||
if (!JWT_SECRET) {
|
||||
throw new Error("JWT_SECRET or BETTER_AUTH_SECRET environment variable is required");
|
||||
}
|
||||
|
||||
// This function will be called with the actual database instance
|
||||
export function createAuth(db: BunSQLiteDatabase) {
|
||||
return betterAuth({
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
import { betterAuth } from "better-auth";
|
||||
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
||||
import { db } from "./db";
|
||||
|
||||
// Generate or use existing JWT secret
|
||||
const JWT_SECRET = process.env.JWT_SECRET || process.env.BETTER_AUTH_SECRET;
|
||||
|
||||
if (!JWT_SECRET) {
|
||||
throw new Error("JWT_SECRET or BETTER_AUTH_SECRET environment variable is required");
|
||||
}
|
||||
import { db, users } from "./db";
|
||||
import * as schema from "./db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
export const auth = betterAuth({
|
||||
// Database configuration
|
||||
database: drizzleAdapter(db, {
|
||||
provider: "sqlite",
|
||||
usePlural: true, // Our tables use plural names (users, not user)
|
||||
schema, // Pass the schema explicitly
|
||||
}),
|
||||
|
||||
// Secret for signing tokens
|
||||
secret: process.env.BETTER_AUTH_SECRET,
|
||||
|
||||
// Base URL configuration
|
||||
baseURL: process.env.BETTER_AUTH_URL || "http://localhost:3000",
|
||||
baseURL: process.env.BETTER_AUTH_URL || "http://localhost:4321",
|
||||
basePath: "/api/auth", // Specify the base path for auth endpoints
|
||||
|
||||
// Authentication methods
|
||||
@@ -31,6 +30,7 @@ export const auth = betterAuth({
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
// Session configuration
|
||||
session: {
|
||||
cookieName: "better-auth-session",
|
||||
@@ -44,9 +44,8 @@ export const auth = betterAuth({
|
||||
// Keep the username field from our existing schema
|
||||
username: {
|
||||
type: "string",
|
||||
required: true,
|
||||
defaultValue: "user", // Default for migration
|
||||
input: true, // Allow in signup form
|
||||
required: false,
|
||||
input: false, // Don't show in signup form - we'll derive from email
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -56,7 +55,7 @@ export const auth = betterAuth({
|
||||
|
||||
// Trusted origins for CORS
|
||||
trustedOrigins: [
|
||||
process.env.BETTER_AUTH_URL || "http://localhost:3000",
|
||||
process.env.BETTER_AUTH_URL || "http://localhost:4321",
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
@@ -18,9 +18,9 @@ export const ENV = {
|
||||
return "sqlite://data/gitea-mirror.db";
|
||||
},
|
||||
|
||||
// JWT secret for authentication
|
||||
JWT_SECRET:
|
||||
process.env.JWT_SECRET || "your-secret-key-change-this-in-production",
|
||||
// Better Auth secret for authentication
|
||||
BETTER_AUTH_SECRET:
|
||||
process.env.BETTER_AUTH_SECRET || "your-secret-key-change-this-in-production",
|
||||
|
||||
// Server host and port
|
||||
HOST: process.env.HOST || "localhost",
|
||||
|
||||
@@ -213,16 +213,18 @@ export const eventSchema = z.object({
|
||||
|
||||
export const users = sqliteTable("users", {
|
||||
id: text("id").primaryKey(),
|
||||
username: text("username").notNull(),
|
||||
password: text("password").notNull(),
|
||||
email: text("email").notNull(),
|
||||
name: text("name"),
|
||||
email: text("email").notNull().unique(),
|
||||
emailVerified: integer("email_verified", { mode: "boolean" }).notNull().default(false),
|
||||
image: text("image"),
|
||||
createdAt: integer("created_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.default(sql`(unixepoch())`),
|
||||
updatedAt: integer("updated_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.default(sql`(unixepoch())`),
|
||||
// Custom fields
|
||||
username: text("username"),
|
||||
});
|
||||
|
||||
export const events = sqliteTable("events", {
|
||||
@@ -463,9 +465,10 @@ export const sessions = sqliteTable("sessions", {
|
||||
// Accounts table (for OAuth providers and credentials)
|
||||
export const accounts = sqliteTable("accounts", {
|
||||
id: text("id").primaryKey(),
|
||||
accountId: text("account_id").notNull(),
|
||||
userId: text("user_id").notNull().references(() => users.id),
|
||||
providerId: text("provider_id").notNull(),
|
||||
providerUserId: text("provider_user_id").notNull(),
|
||||
providerUserId: text("provider_user_id"), // Make nullable for email/password auth
|
||||
accessToken: text("access_token"),
|
||||
refreshToken: text("refresh_token"),
|
||||
expiresAt: integer("expires_at", { mode: "timestamp" }),
|
||||
@@ -478,6 +481,7 @@ export const accounts = sqliteTable("accounts", {
|
||||
.default(sql`(unixepoch())`),
|
||||
}, (table) => {
|
||||
return {
|
||||
accountIdIdx: index("idx_accounts_account_id").on(table.accountId),
|
||||
userIdIdx: index("idx_accounts_user_id").on(table.userId),
|
||||
providerIdx: index("idx_accounts_provider").on(table.providerId, table.providerUserId),
|
||||
};
|
||||
|
||||
72
src/pages/api/auth/debug.ts
Normal file
72
src/pages/api/auth/debug.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import type { APIRoute } from "astro";
|
||||
import { auth } from "@/lib/auth";
|
||||
import { db } from "@/lib/db";
|
||||
import { users } from "@/lib/db/schema";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
export const GET: APIRoute = async ({ request }) => {
|
||||
try {
|
||||
// Get Better Auth configuration info
|
||||
const info = {
|
||||
baseURL: auth.options.baseURL,
|
||||
basePath: auth.options.basePath,
|
||||
trustedOrigins: auth.options.trustedOrigins,
|
||||
emailPasswordEnabled: auth.options.emailAndPassword?.enabled,
|
||||
userFields: auth.options.user?.additionalFields,
|
||||
databaseConfig: {
|
||||
usePlural: true,
|
||||
provider: "sqlite"
|
||||
}
|
||||
};
|
||||
|
||||
return new Response(JSON.stringify({
|
||||
success: true,
|
||||
config: info
|
||||
}), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
} catch (error) {
|
||||
return new Response(JSON.stringify({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : "Unknown error",
|
||||
}), {
|
||||
status: 500,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const POST: APIRoute = async ({ request }) => {
|
||||
try {
|
||||
// Test creating a user directly
|
||||
const userId = nanoid();
|
||||
const now = new Date();
|
||||
|
||||
await db.insert(users).values({
|
||||
id: userId,
|
||||
email: "test2@example.com",
|
||||
emailVerified: false,
|
||||
username: "test2",
|
||||
// Let the database handle timestamps with defaults
|
||||
});
|
||||
|
||||
return new Response(JSON.stringify({
|
||||
success: true,
|
||||
userId,
|
||||
message: "User created successfully"
|
||||
}), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
} catch (error) {
|
||||
return new Response(JSON.stringify({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : "Unknown error",
|
||||
details: error
|
||||
}), {
|
||||
status: 500,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
import '../styles/global.css';
|
||||
import ThemeScript from '@/components/theme/ThemeScript.astro';
|
||||
import { LoginForm } from '@/components/auth/LoginForm';
|
||||
import { LoginPage } from '@/components/auth/LoginPage';
|
||||
import { db, users } from '@/lib/db';
|
||||
import { sql } from 'drizzle-orm';
|
||||
|
||||
@@ -30,7 +30,7 @@ const generator = Astro.generator;
|
||||
</head>
|
||||
<body>
|
||||
<div class="h-dvh flex items-center justify-center bg-muted/30 p-4">
|
||||
<LoginForm client:load />
|
||||
<LoginPage client:load />
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
import '../styles/global.css';
|
||||
import ThemeScript from '@/components/theme/ThemeScript.astro';
|
||||
import { SignupForm } from '@/components/auth/SignupForm';
|
||||
import { SignupPage } from '@/components/auth/SignupPage';
|
||||
import { db, users } from '@/lib/db';
|
||||
import { sql } from 'drizzle-orm';
|
||||
|
||||
@@ -34,7 +34,7 @@ const generator = Astro.generator;
|
||||
<h1 class="text-3xl font-bold mb-2">Welcome to Gitea Mirror</h1>
|
||||
<p class="text-muted-foreground">Let's set up your administrator account to get started.</p>
|
||||
</div>
|
||||
<SignupForm client:load />
|
||||
<SignupPage client:load />
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user