mirror of
https://github.com/RayLabsHQ/gitea-mirror.git
synced 2025-12-06 11:36: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
|
DATABASE_URL=sqlite://data/gitea-mirror.db
|
||||||
|
|
||||||
# Security
|
# 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_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)
|
# 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.
|
# 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
|
- DATABASE_URL=file:data/gitea-mirror.db
|
||||||
- HOST=0.0.0.0
|
- HOST=0.0.0.0
|
||||||
- PORT=4321
|
- 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:
|
healthcheck:
|
||||||
test: ["CMD", "wget", "--no-verbose", "--tries=3", "--spider", "http://localhost:4321/api/health"]
|
test: ["CMD", "wget", "--no-verbose", "--tries=3", "--spider", "http://localhost:4321/api/health"]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ services:
|
|||||||
- DATABASE_URL=file:data/gitea-mirror.db
|
- DATABASE_URL=file:data/gitea-mirror.db
|
||||||
- HOST=0.0.0.0
|
- HOST=0.0.0.0
|
||||||
- PORT=4321
|
- PORT=4321
|
||||||
- JWT_SECRET=dev-secret-key
|
- BETTER_AUTH_SECRET=dev-secret-key
|
||||||
# GitHub/Gitea Mirror Config
|
# GitHub/Gitea Mirror Config
|
||||||
- GITHUB_USERNAME=${GITHUB_USERNAME:-your-github-username}
|
- GITHUB_USERNAME=${GITHUB_USERNAME:-your-github-username}
|
||||||
- GITHUB_TOKEN=${GITHUB_TOKEN:-your-github-token}
|
- GITHUB_TOKEN=${GITHUB_TOKEN:-your-github-token}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ services:
|
|||||||
- DATABASE_URL=file:data/gitea-mirror.db
|
- DATABASE_URL=file:data/gitea-mirror.db
|
||||||
- HOST=0.0.0.0
|
- HOST=0.0.0.0
|
||||||
- PORT=4321
|
- 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/Gitea Mirror Config
|
||||||
- GITHUB_USERNAME=${GITHUB_USERNAME:-}
|
- GITHUB_USERNAME=${GITHUB_USERNAME:-}
|
||||||
- GITHUB_TOKEN=${GITHUB_TOKEN:-}
|
- GITHUB_TOKEN=${GITHUB_TOKEN:-}
|
||||||
|
|||||||
@@ -52,15 +52,26 @@ if [ "$GITEA_SKIP_TLS_VERIFY" = "true" ]; then
|
|||||||
export NODE_TLS_REJECT_UNAUTHORIZED=0
|
export NODE_TLS_REJECT_UNAUTHORIZED=0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Generate a secure JWT secret if one isn't provided or is using the default value
|
# Generate a secure BETTER_AUTH_SECRET if one isn't provided or is using the default value
|
||||||
JWT_SECRET_FILE="/app/data/.jwt_secret"
|
BETTER_AUTH_SECRET_FILE="/app/data/.better_auth_secret"
|
||||||
if [ "$JWT_SECRET" = "your-secret-key-change-this-in-production" ] || [ -z "$JWT_SECRET" ]; then
|
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
|
# Check if we have a previously generated secret
|
||||||
if [ -f "$JWT_SECRET_FILE" ]; then
|
if [ -f "$BETTER_AUTH_SECRET_FILE" ]; then
|
||||||
echo "Using previously generated JWT secret"
|
echo "Using previously generated BETTER_AUTH_SECRET"
|
||||||
export JWT_SECRET=$(cat "$JWT_SECRET_FILE")
|
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
|
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
|
# Try to generate a secure random string using OpenSSL
|
||||||
if command -v openssl >/dev/null 2>&1; then
|
if command -v openssl >/dev/null 2>&1; then
|
||||||
GENERATED_SECRET=$(openssl rand -hex 32)
|
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"
|
echo "OpenSSL not found, using fallback method for random generation"
|
||||||
GENERATED_SECRET=$(head -c 32 /dev/urandom | sha256sum | cut -d' ' -f1)
|
GENERATED_SECRET=$(head -c 32 /dev/urandom | sha256sum | cut -d' ' -f1)
|
||||||
fi
|
fi
|
||||||
export JWT_SECRET="$GENERATED_SECRET"
|
export BETTER_AUTH_SECRET="$GENERATED_SECRET"
|
||||||
# Save the secret to a file for persistence across container restarts
|
# Save the secret to a file for persistence across container restarts
|
||||||
echo "$GENERATED_SECRET" > "$JWT_SECRET_FILE"
|
echo "$GENERATED_SECRET" > "$BETTER_AUTH_SECRET_FILE"
|
||||||
chmod 600 "$JWT_SECRET_FILE"
|
chmod 600 "$BETTER_AUTH_SECRET_FILE"
|
||||||
fi
|
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
|
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` (
|
CREATE TABLE `configs` (
|
||||||
`id` text PRIMARY KEY NOT NULL,
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
`user_id` text 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_organization` ON `repositories` (`organization`);--> statement-breakpoint
|
||||||
CREATE INDEX `idx_repositories_is_fork` ON `repositories` (`is_fork`);--> 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 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` (
|
CREATE TABLE `users` (
|
||||||
`id` text PRIMARY KEY NOT NULL,
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
`username` text NOT NULL,
|
`name` text,
|
||||||
`password` text NOT NULL,
|
|
||||||
`email` text NOT NULL,
|
`email` text NOT NULL,
|
||||||
|
`email_verified` integer DEFAULT false NOT NULL,
|
||||||
|
`image` text,
|
||||||
`created_at` integer DEFAULT (unixepoch()) NOT NULL,
|
`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",
|
"version": "6",
|
||||||
"dialect": "sqlite",
|
"dialect": "sqlite",
|
||||||
"id": "b963d828-412d-4192-b0aa-3b13b83cfba8",
|
"id": "7782b8ba-bdae-42e8-b8a7-614f8be30a58",
|
||||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||||
"tables": {
|
"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": {
|
"configs": {
|
||||||
"name": "configs",
|
"name": "configs",
|
||||||
"columns": {
|
"columns": {
|
||||||
@@ -887,8 +1013,8 @@
|
|||||||
"uniqueConstraints": {},
|
"uniqueConstraints": {},
|
||||||
"checkConstraints": {}
|
"checkConstraints": {}
|
||||||
},
|
},
|
||||||
"users": {
|
"sessions": {
|
||||||
"name": "users",
|
"name": "sessions",
|
||||||
"columns": {
|
"columns": {
|
||||||
"id": {
|
"id": {
|
||||||
"name": "id",
|
"name": "id",
|
||||||
@@ -897,27 +1023,41 @@
|
|||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"username": {
|
"token": {
|
||||||
"name": "username",
|
"name": "token",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"password": {
|
"user_id": {
|
||||||
"name": "password",
|
"name": "user_id",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"email": {
|
"expires_at": {
|
||||||
"name": "email",
|
"name": "expires_at",
|
||||||
"type": "text",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"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": {
|
"created_at": {
|
||||||
"name": "created_at",
|
"name": "created_at",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
@@ -935,7 +1075,202 @@
|
|||||||
"default": "(unixepoch())"
|
"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": {},
|
"foreignKeys": {},
|
||||||
"compositePrimaryKeys": {},
|
"compositePrimaryKeys": {},
|
||||||
"uniqueConstraints": {},
|
"uniqueConstraints": {},
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -5,15 +5,8 @@
|
|||||||
{
|
{
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"when": 1752161775910,
|
"when": 1752171873627,
|
||||||
"tag": "0000_big_xorn",
|
"tag": "0000_init",
|
||||||
"breakpoints": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 1,
|
|
||||||
"version": "6",
|
|
||||||
"when": 1752166860985,
|
|
||||||
"tag": "0001_vengeful_whirlwind",
|
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ CONTAINER="gitea-test"
|
|||||||
IMAGE="ubuntu:22.04"
|
IMAGE="ubuntu:22.04"
|
||||||
INSTALL_DIR="/opt/gitea-mirror"
|
INSTALL_DIR="/opt/gitea-mirror"
|
||||||
PORT=4321
|
PORT=4321
|
||||||
JWT_SECRET="$(openssl rand -hex 32)"
|
BETTER_AUTH_SECRET="$(openssl rand -hex 32)"
|
||||||
|
|
||||||
BUN_ZIP="/tmp/bun-linux-x64.zip"
|
BUN_ZIP="/tmp/bun-linux-x64.zip"
|
||||||
BUN_URL="https://github.com/oven-sh/bun/releases/latest/download/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=HOST=0.0.0.0
|
||||||
Environment=PORT=$PORT
|
Environment=PORT=$PORT
|
||||||
Environment=DATABASE_URL=file:data/gitea-mirror.db
|
Environment=DATABASE_URL=file:data/gitea-mirror.db
|
||||||
Environment=JWT_SECRET=$JWT_SECRET
|
Environment=BETTER_AUTH_SECRET=$BETTER_AUTH_SECRET
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
SERVICE
|
SERVICE
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export function LoginForm() {
|
|||||||
toast.success('Login successful!');
|
toast.success('Login successful!');
|
||||||
// Small delay before redirecting to see the success message
|
// Small delay before redirecting to see the success message
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
window.location.href = '/dashboard';
|
window.location.href = '/';
|
||||||
}, 1000);
|
}, 1000);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorToast(error, toast);
|
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);
|
setIsLoading(true);
|
||||||
const form = e.currentTarget;
|
const form = e.currentTarget;
|
||||||
const formData = new FormData(form);
|
const formData = new FormData(form);
|
||||||
const username = formData.get('username') as string | null;
|
|
||||||
const email = formData.get('email') as string | null;
|
const email = formData.get('email') as string | null;
|
||||||
const password = formData.get('password') as string | null;
|
const password = formData.get('password') as string | null;
|
||||||
const confirmPassword = formData.get('confirmPassword') 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');
|
toast.error('Please fill in all fields');
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
return;
|
return;
|
||||||
@@ -34,11 +33,13 @@ export function SignupForm() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Derive username from email (part before @)
|
||||||
|
const username = email.split('@')[0];
|
||||||
await register(username, email, password);
|
await register(username, email, password);
|
||||||
toast.success('Account created successfully! Redirecting to dashboard...');
|
toast.success('Account created successfully! Redirecting to dashboard...');
|
||||||
// Small delay before redirecting to see the success message
|
// Small delay before redirecting to see the success message
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
window.location.href = '/dashboard';
|
window.location.href = '/';
|
||||||
}, 1500);
|
}, 1500);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorToast(error, toast);
|
showErrorToast(error, toast);
|
||||||
@@ -71,20 +72,6 @@ export function SignupForm() {
|
|||||||
<CardContent>
|
<CardContent>
|
||||||
<form id="signup-form" onSubmit={handleSignup}>
|
<form id="signup-form" onSubmit={handleSignup}>
|
||||||
<div className="space-y-4">
|
<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>
|
<div>
|
||||||
<label htmlFor="email" className="block text-sm font-medium mb-1">
|
<label htmlFor="email" className="block text-sm font-medium mb-1">
|
||||||
Email
|
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"
|
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"
|
placeholder="Enter your email"
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
|
autoFocus
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<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 user = betterAuthSession.data?.user || null;
|
||||||
const session = betterAuthSession.data || null;
|
const session = betterAuthSession.data || null;
|
||||||
|
|
||||||
// Check if this is first load and redirect if needed
|
// Don't do any redirects here - let the pages handle their own redirect logic
|
||||||
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]);
|
|
||||||
|
|
||||||
const login = async (email: string, password: string) => {
|
const login = async (email: string, password: string) => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
@@ -67,7 +46,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
const result = await authClient.signIn.email({
|
const result = await authClient.signIn.email({
|
||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
callbackURL: "/dashboard",
|
callbackURL: "/",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
@@ -93,9 +72,8 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
const result = await authClient.signUp.email({
|
const result = await authClient.signUp.email({
|
||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
name: username, // Better Auth uses 'name' field
|
name: username, // Better Auth uses 'name' field for display name
|
||||||
username, // Also pass username as additional field
|
callbackURL: "/",
|
||||||
callbackURL: "/dashboard",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
|
|||||||
@@ -3,13 +3,6 @@ import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
|||||||
import { sso, oidcProvider } from "better-auth/plugins";
|
import { sso, oidcProvider } from "better-auth/plugins";
|
||||||
import type { BunSQLiteDatabase } from "drizzle-orm/bun-sqlite";
|
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
|
// This function will be called with the actual database instance
|
||||||
export function createAuth(db: BunSQLiteDatabase) {
|
export function createAuth(db: BunSQLiteDatabase) {
|
||||||
return betterAuth({
|
return betterAuth({
|
||||||
|
|||||||
@@ -1,23 +1,22 @@
|
|||||||
import { betterAuth } from "better-auth";
|
import { betterAuth } from "better-auth";
|
||||||
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
||||||
import { db } from "./db";
|
import { db, users } from "./db";
|
||||||
|
import * as schema from "./db/schema";
|
||||||
// Generate or use existing JWT secret
|
import { eq } from "drizzle-orm";
|
||||||
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");
|
|
||||||
}
|
|
||||||
|
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
// Database configuration
|
// Database configuration
|
||||||
database: drizzleAdapter(db, {
|
database: drizzleAdapter(db, {
|
||||||
provider: "sqlite",
|
provider: "sqlite",
|
||||||
usePlural: true, // Our tables use plural names (users, not user)
|
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
|
// 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
|
basePath: "/api/auth", // Specify the base path for auth endpoints
|
||||||
|
|
||||||
// Authentication methods
|
// Authentication methods
|
||||||
@@ -31,6 +30,7 @@ export const auth = betterAuth({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
// Session configuration
|
// Session configuration
|
||||||
session: {
|
session: {
|
||||||
cookieName: "better-auth-session",
|
cookieName: "better-auth-session",
|
||||||
@@ -44,9 +44,8 @@ export const auth = betterAuth({
|
|||||||
// Keep the username field from our existing schema
|
// Keep the username field from our existing schema
|
||||||
username: {
|
username: {
|
||||||
type: "string",
|
type: "string",
|
||||||
required: true,
|
required: false,
|
||||||
defaultValue: "user", // Default for migration
|
input: false, // Don't show in signup form - we'll derive from email
|
||||||
input: true, // Allow in signup form
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -56,7 +55,7 @@ export const auth = betterAuth({
|
|||||||
|
|
||||||
// Trusted origins for CORS
|
// Trusted origins for CORS
|
||||||
trustedOrigins: [
|
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";
|
return "sqlite://data/gitea-mirror.db";
|
||||||
},
|
},
|
||||||
|
|
||||||
// JWT secret for authentication
|
// Better Auth secret for authentication
|
||||||
JWT_SECRET:
|
BETTER_AUTH_SECRET:
|
||||||
process.env.JWT_SECRET || "your-secret-key-change-this-in-production",
|
process.env.BETTER_AUTH_SECRET || "your-secret-key-change-this-in-production",
|
||||||
|
|
||||||
// Server host and port
|
// Server host and port
|
||||||
HOST: process.env.HOST || "localhost",
|
HOST: process.env.HOST || "localhost",
|
||||||
|
|||||||
@@ -213,16 +213,18 @@ export const eventSchema = z.object({
|
|||||||
|
|
||||||
export const users = sqliteTable("users", {
|
export const users = sqliteTable("users", {
|
||||||
id: text("id").primaryKey(),
|
id: text("id").primaryKey(),
|
||||||
username: text("username").notNull(),
|
name: text("name"),
|
||||||
password: text("password").notNull(),
|
email: text("email").notNull().unique(),
|
||||||
email: text("email").notNull(),
|
|
||||||
emailVerified: integer("email_verified", { mode: "boolean" }).notNull().default(false),
|
emailVerified: integer("email_verified", { mode: "boolean" }).notNull().default(false),
|
||||||
|
image: text("image"),
|
||||||
createdAt: integer("created_at", { mode: "timestamp" })
|
createdAt: integer("created_at", { mode: "timestamp" })
|
||||||
.notNull()
|
.notNull()
|
||||||
.default(sql`(unixepoch())`),
|
.default(sql`(unixepoch())`),
|
||||||
updatedAt: integer("updated_at", { mode: "timestamp" })
|
updatedAt: integer("updated_at", { mode: "timestamp" })
|
||||||
.notNull()
|
.notNull()
|
||||||
.default(sql`(unixepoch())`),
|
.default(sql`(unixepoch())`),
|
||||||
|
// Custom fields
|
||||||
|
username: text("username"),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const events = sqliteTable("events", {
|
export const events = sqliteTable("events", {
|
||||||
@@ -463,9 +465,10 @@ export const sessions = sqliteTable("sessions", {
|
|||||||
// Accounts table (for OAuth providers and credentials)
|
// Accounts table (for OAuth providers and credentials)
|
||||||
export const accounts = sqliteTable("accounts", {
|
export const accounts = sqliteTable("accounts", {
|
||||||
id: text("id").primaryKey(),
|
id: text("id").primaryKey(),
|
||||||
|
accountId: text("account_id").notNull(),
|
||||||
userId: text("user_id").notNull().references(() => users.id),
|
userId: text("user_id").notNull().references(() => users.id),
|
||||||
providerId: text("provider_id").notNull(),
|
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"),
|
accessToken: text("access_token"),
|
||||||
refreshToken: text("refresh_token"),
|
refreshToken: text("refresh_token"),
|
||||||
expiresAt: integer("expires_at", { mode: "timestamp" }),
|
expiresAt: integer("expires_at", { mode: "timestamp" }),
|
||||||
@@ -478,6 +481,7 @@ export const accounts = sqliteTable("accounts", {
|
|||||||
.default(sql`(unixepoch())`),
|
.default(sql`(unixepoch())`),
|
||||||
}, (table) => {
|
}, (table) => {
|
||||||
return {
|
return {
|
||||||
|
accountIdIdx: index("idx_accounts_account_id").on(table.accountId),
|
||||||
userIdIdx: index("idx_accounts_user_id").on(table.userId),
|
userIdIdx: index("idx_accounts_user_id").on(table.userId),
|
||||||
providerIdx: index("idx_accounts_provider").on(table.providerId, table.providerUserId),
|
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 '../styles/global.css';
|
||||||
import ThemeScript from '@/components/theme/ThemeScript.astro';
|
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 { db, users } from '@/lib/db';
|
||||||
import { sql } from 'drizzle-orm';
|
import { sql } from 'drizzle-orm';
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ const generator = Astro.generator;
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="h-dvh flex items-center justify-center bg-muted/30 p-4">
|
<div class="h-dvh flex items-center justify-center bg-muted/30 p-4">
|
||||||
<LoginForm client:load />
|
<LoginPage client:load />
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
import '../styles/global.css';
|
import '../styles/global.css';
|
||||||
import ThemeScript from '@/components/theme/ThemeScript.astro';
|
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 { db, users } from '@/lib/db';
|
||||||
import { sql } from 'drizzle-orm';
|
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>
|
<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>
|
<p class="text-muted-foreground">Let's set up your administrator account to get started.</p>
|
||||||
</div>
|
</div>
|
||||||
<SignupForm client:load />
|
<SignupPage client:load />
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user