Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
0decd30
feat(auth): setup authentication with better-auth
edwintantawi Jan 18, 2026
54c375d
Merge branch 'dev' of github.com:devsantara/kit into feat/auth
edwintantawi Jan 24, 2026
ec54e59
Merge branch 'dev' of github.com:devsantara/kit into feat/auth
edwintantawi Jan 24, 2026
b5f8a8f
feat(auth): adjust min password length
edwintantawi Jan 26, 2026
d9e66e2
feat(form): add destructive error alert border
edwintantawi Jan 26, 2026
d8d1ed0
fix: broken lockfile
edwintantawi Jan 27, 2026
b577337
feat(i18n): add messages for auth field and errors
edwintantawi Jan 28, 2026
4efe40f
feat(auth): add error message map translation
edwintantawi Jan 28, 2026
4e5bb17
feat(auth): add authentication mechanism
edwintantawi Jan 30, 2026
22f2d4b
feat(auth): refine layout
edwintantawi Jan 30, 2026
2b3cf92
feat(app): refine layout
edwintantawi Jan 30, 2026
4fe5f5a
refactor(import): use icon suffix for icon import
edwintantawi Jan 30, 2026
6a4857d
feat(auth): disable social provider for now
edwintantawi Feb 10, 2026
223e491
refactor(auth): extract auth and guest guards into reusable server fn
edwintantawi Feb 12, 2026
afde22d
refactor(auth): guard as utils
edwintantawi Feb 17, 2026
9dfb367
feat(auth): remove unused schema
edwintantawi Feb 19, 2026
dd29f3c
feat(auth): add google and github social provider
edwintantawi Feb 19, 2026
61f7ce0
refactor(auth): add auth prefix for envs
edwintantawi Feb 19, 2026
58c072f
ci: add auth envs
edwintantawi Feb 19, 2026
3036140
feat(auth): add secondary storage and ratelimit
edwintantawi Feb 20, 2026
b97e6e4
feat(auth): guard redirect should be replace history
edwintantawi Feb 20, 2026
c920348
feat(auth): add minimal auth ttl
edwintantawi Feb 20, 2026
4a19fd5
Merge branch 'dev' of github.com:devsantara/kit into feat/auth
edwintantawi Feb 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,12 @@ POSTHOG_CLI_ENV_ID=<***>
# Personal API key with error tracking write and organization read scopes
# See https://app.posthog.com/settings/user-api-keys#variables
POSTHOG_CLI_TOKEN=<***>

# Auth - Better Auth
# Generate the secret using `openssl rand -base64 32`.
AUTH_SECRET=<***>
AUTH_GITHUB_CLIENT_ID=<***>
AUTH_GITHUB_CLIENT_SECRET=<***>
AUTH_GOOGLE_CLIENT_ID=<***>
AUTH_GOOGLE_CLIENT_SECRET=<***>

6 changes: 6 additions & 0 deletions .github/workflows/deployment-preview.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,9 @@ jobs:
POSTHOG_CLI_HOST: ${{ vars.POSTHOG_CLI_HOST }}
POSTHOG_CLI_ENV_ID: ${{ secrets.POSTHOG_CLI_ENV_ID }}
POSTHOG_CLI_TOKEN: ${{ secrets.POSTHOG_CLI_TOKEN }}
# Auth
AUTH_SECRET: ${{ secrets.AUTH_SECRET }}
AUTH_GITHUB_CLIENT_ID: ${{ secrets.AUTH_GITHUB_CLIENT_ID }}
AUTH_GITHUB_CLIENT_SECRET: ${{ secrets.AUTH_GITHUB_CLIENT_SECRET }}
AUTH_GOOGLE_CLIENT_ID: ${{ secrets.AUTH_GOOGLE_CLIENT_ID }}
AUTH_GOOGLE_CLIENT_SECRET: ${{ secrets.AUTH_GOOGLE_CLIENT_SECRET }}
6 changes: 6 additions & 0 deletions .github/workflows/deployment-production.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,9 @@ jobs:
POSTHOG_CLI_HOST: ${{ vars.POSTHOG_CLI_HOST }}
POSTHOG_CLI_ENV_ID: ${{ secrets.POSTHOG_CLI_ENV_ID }}
POSTHOG_CLI_TOKEN: ${{ secrets.POSTHOG_CLI_TOKEN }}
# Auth
AUTH_SECRET: ${{ secrets.AUTH_SECRET }}
AUTH_GITHUB_CLIENT_ID: ${{ secrets.AUTH_GITHUB_CLIENT_ID }}
AUTH_GITHUB_CLIENT_SECRET: ${{ secrets.AUTH_GITHUB_CLIENT_SECRET }}
AUTH_GOOGLE_CLIENT_ID: ${{ secrets.AUTH_GOOGLE_CLIENT_ID }}
AUTH_GOOGLE_CLIENT_SECRET: ${{ secrets.AUTH_GOOGLE_CLIENT_SECRET }}
24 changes: 23 additions & 1 deletion alchemy.run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,25 @@
*/

import alchemy, { type Scope } from 'alchemy';
import { D1Database, TanStackStart } from 'alchemy/cloudflare';
import { D1Database, KVNamespace, TanStackStart } from 'alchemy/cloudflare';
import { GitHubComment } from 'alchemy/github';
import { CloudflareStateStore, FileSystemStateStore } from 'alchemy/state';

import packageJson from './package.json' with { type: 'json' };
import { alchemyEnv } from './src/lib/env/alchemy.ts';
import { serverEnv } from './src/lib/env/server.ts';

const ALCHEMY_SECRET = alchemyEnv.ALCHEMY_SECRET;
const ALCHEMY_STATE_TOKEN = alchemy.secret(alchemyEnv.ALCHEMY_STATE_TOKEN);
const AUTH_SECRET = alchemy.secret(serverEnv.AUTH_SECRET);
const AUTH_GITHUB_CLIENT_ID = alchemy.secret(serverEnv.AUTH_GITHUB_CLIENT_ID);
const AUTH_GITHUB_CLIENT_SECRET = alchemy.secret(
serverEnv.AUTH_GITHUB_CLIENT_SECRET,
);
const AUTH_GOOGLE_CLIENT_ID = alchemy.secret(serverEnv.AUTH_GOOGLE_CLIENT_ID);
const AUTH_GOOGLE_CLIENT_SECRET = alchemy.secret(
serverEnv.AUTH_GOOGLE_CLIENT_SECRET,
);

function isProductionStage(scope: Scope) {
return scope.stage === 'production';
Expand Down Expand Up @@ -50,14 +60,26 @@ const database = await D1Database('database', {
},
});

const kv = await KVNamespace('kv', {
adopt: true,
});

export const worker = await TanStackStart('website', {
adopt: true,
observability: isProductionStage(app) ? { enabled: true } : undefined,
url: isProductionStage(app) ? false : true,
domains: isProductionStage(app) ? [alchemyEnv.HOSTNAME] : undefined,
placement: isProductionStage(app) ? { mode: 'smart' } : undefined,
bindings: {
// Services
DATABASE: database,
KV: kv,
// Environment variables
AUTH_SECRET: AUTH_SECRET,
AUTH_GITHUB_CLIENT_ID: AUTH_GITHUB_CLIENT_ID,
AUTH_GITHUB_CLIENT_SECRET: AUTH_GITHUB_CLIENT_SECRET,
AUTH_GOOGLE_CLIENT_ID: AUTH_GOOGLE_CLIENT_ID,
AUTH_GOOGLE_CLIENT_SECRET: AUTH_GOOGLE_CLIENT_SECRET,
},
});

Expand Down
94 changes: 93 additions & 1 deletion messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,97 @@

"common_error_something_went_wrong": "Something went wrong",
"common_error_form_validation_title": "There is something wrong with the form",
"common_error_form_validation_description": "Please review the form and correct them to continue."
"common_error_form_validation_description": "Please review the form and correct them to continue.",

"common_greeting_name": "Hi, {name}!",
"common_continue_as_name": "Continue as {name}",

"auth_get_started_title": "Let's Get You Started",
"auth_get_started_description": "Choose how you'd like to continue",
"auth_get_started_action": "Get Started",
"auth_get_started_signup_title": "Create Your Account",
"auth_get_started_signup_description": "New here? Sign up to create your account and start your journey with us.",
"auth_get_started_signin_title": "Existing Account",
"auth_get_started_signin_description": "Already have an account? Sign in to pick up right where you left off.",

"auth_continue_with_social_provider": "Continue with {provider}",
"auth_or_separator": "Or",

"auth_field_full_name_label": "Full name",
"auth_field_full_name_placeholder": "Devsantara Team",
"auth_field_email_label": "Email",
"auth_field_email_placeholder": "team@devsantara.com",
"auth_field_password_label": "Password",
"auth_field_confirm_password_label": "Confirm Password",
"auth_field_error_name_required": "Name is required",
"auth_field_error_email_invalid": "Please enter a valid email address",
"auth_field_error_password_min_length": "Password must be at least {min} characters",
"auth_field_error_confirm_password_not_match": "Password and confirmation password do not match",

"auth_sign_up_title": "Create your account",
"auth_sign_up_description": "Fill out the form below to create your account",
"auth_sign_up_action": "Create Account",
"auth_sign_up_already_have_account": "Already have an account?",
"auth_sign_up_sign_in_link": "Sign in",
"auth_sign_up_error_fail": "Sign up failed",
"auth_sign_up_success_title": "Account created successfully",
"auth_sign_up_success_description": "Please sign in to continue.",

"auth_sign_in_title": "Sign in to your account",
"auth_sign_in_description": "Please enter your details to continue.",
"auth_sign_in_action": "Sign in",
"auth_sign_in_fail": "Sign in failed",
"auth_sign_in_success_title": "Welcome back!",
"auth_sign_in_success_description": "We're happy to see you again.",
"auth_sign_in_no_account": "Don't have an account?",
"auth_sign_in_sign_up_link": "Sign up",

"auth_sign_out_action": "Sign out",
"auth_sign_out_error_fail": "Sign out failed",
"auth_sign_out_success_title": "You've been signed out",
"auth_sign_out_success_description": "We hope to see you again soon.",

"auth_error_base_user_not_found": "User not found",
"auth_error_base_failed_to_create_user": "Failed to create user",
"auth_error_base_failed_to_create_session": "Failed to create session",
"auth_error_base_failed_to_update_user": "Failed to update user",
"auth_error_base_failed_to_get_session": "Failed to get session",
"auth_error_base_invalid_password": "Invalid password",
"auth_error_base_invalid_email": "Invalid email",
"auth_error_base_invalid_email_or_password": "Invalid email or password",
"auth_error_base_social_account_already_linked": "Social account already linked",
"auth_error_base_provider_not_found": "Provider not found",
"auth_error_base_invalid_token": "Invalid token",
"auth_error_base_id_token_not_supported": "id_token not supported",
"auth_error_base_failed_to_get_user_info": "Failed to get user info",
"auth_error_base_user_email_not_found": "User email not found",
"auth_error_base_email_not_verified": "Email not verified",
"auth_error_base_password_too_short": "Password too short",
"auth_error_base_password_too_long": "Password too long",
"auth_error_base_user_already_exists": "User already exists.",
"auth_error_base_user_already_exists_use_another_email": "User already exists. Use another email.",
"auth_error_base_email_can_not_be_updated": "Email can not be updated",
"auth_error_base_credential_account_not_found": "Credential account not found",
"auth_error_base_session_expired": "Session expired. Re-authenticate to perform this action.",
"auth_error_base_failed_to_unlink_last_account": "You can't unlink your last account",
"auth_error_base_account_not_found": "Account not found",
"auth_error_base_user_already_has_password": "User already has a password. Provide that to delete the account.",
"auth_error_base_cross_site_navigation_login_blocked": "Cross-site navigation login blocked. This request appears to be a CSRF attack.",
"auth_error_base_verification_email_not_enabled": "Verification email isn't enabled",
"auth_error_base_email_already_verified": "Email is already verified",
"auth_error_base_email_mismatch": "Email mismatch",
"auth_error_base_session_not_fresh": "Session is not fresh",
"auth_error_base_linked_account_already_exists": "Linked account already exists",
"auth_error_base_invalid_origin": "Invalid origin",
"auth_error_base_invalid_callback_url": "Invalid callbackURL",
"auth_error_base_invalid_redirect_url": "Invalid redirectURL",
"auth_error_base_invalid_error_callback_url": "Invalid errorCallbackURL",
"auth_error_base_invalid_new_user_callback_url": "Invalid newUserCallbackURL",
"auth_error_base_missing_or_null_origin": "Missing or null Origin",
"auth_error_base_callback_url_required": "callbackURL is required",
"auth_error_base_failed_to_create_verification": "Unable to create verification",
"auth_error_base_field_not_allowed": "Field not allowed to be set",
"auth_error_base_async_validation_not_supported": "Async validation is not supported",
"auth_error_base_validation_error": "Validation Error",
"auth_error_base_missing_field": "Field is required"
}
94 changes: 93 additions & 1 deletion messages/id.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,97 @@

"common_error_something_went_wrong": "Terjadi kesalahan",
"common_error_form_validation_title": "Ada kesalahan pada formulir",
"common_error_form_validation_description": "Silakan periksa kembali formulir dan perbaiki untuk melanjutkan."
"common_error_form_validation_description": "Silakan periksa kembali formulir dan perbaiki untuk melanjutkan.",

"common_greeting_name": "Hai, {name}!",
"common_continue_as_name": "Lanjutkan sebagai {name}",

"auth_get_started_title": "Mari Memulai",
"auth_get_started_description": "Pilih cara untuk melanjutkan",
"auth_get_started_action": "Mulai",
"auth_get_started_signup_title": "Buat Akun Anda",
"auth_get_started_signup_description": "Baru di sini? Daftar untuk membuat akun dan mulai perjalanan Anda bersama kami.",
"auth_get_started_signin_title": "Akun yang Ada",
"auth_get_started_signin_description": "Sudah punya akun? Masuk untuk melanjutkan dari terakhir kali Anda berhenti.",

"auth_continue_with_social_provider": "Lanjutkan dengan {provider}",
"auth_or_separator": "Atau",

"auth_field_full_name_label": "Nama lengkap",
"auth_field_full_name_placeholder": "Tim Devsantara",
"auth_field_email_label": "Email",
"auth_field_email_placeholder": "team@devsantara.com",
"auth_field_password_label": "Kata sandi",
"auth_field_confirm_password_label": "Konfirmasi Kata Sandi",
"auth_field_error_name_required": "Nama wajib diisi",
"auth_field_error_email_invalid": "Silakan masukkan alamat email yang valid",
"auth_field_error_password_min_length": "Password harus memiliki minimal {min} karakter",
"auth_field_error_confirm_password_not_match": "Password dan konfirmasi password Anda tidak sama",

"auth_sign_up_title": "Buat akun Anda",
"auth_sign_up_description": "Isi formulir di bawah untuk membuat akun Anda",
"auth_sign_up_action": "Buat Akun",
"auth_sign_up_already_have_account": "Sudah punya akun?",
"auth_sign_up_sign_in_link": "Masuk",
"auth_sign_up_error_fail": "Pendaftaran gagal",
"auth_sign_up_success_title": "Akun berhasil dibuat",
"auth_sign_up_success_description": "Silakan masuk untuk melanjutkan.",

"auth_sign_in_title": "Masuk ke akun Anda",
"auth_sign_in_description": "Silakan masukkan detail Anda untuk melanjutkan.",
"auth_sign_in_action": "Masuk",
"auth_sign_in_fail": "Gagal masuk",
"auth_sign_in_success_title": "Selamat datang kembali!",
"auth_sign_in_success_description": "Senang melihat Anda kembali.",
"auth_sign_in_no_account": "Belum punya akun?",
"auth_sign_in_sign_up_link": "Daftar",

"auth_sign_out_action": "Keluar",
"auth_sign_out_error_fail": "Gagal keluar",
"auth_sign_out_success_title": "Berhasil keluar",
"auth_sign_out_success_description": "Sampai jumpa lagi.",

"auth_error_base_user_not_found": "Pengguna tidak ditemukan",
"auth_error_base_failed_to_create_user": "Gagal membuat pengguna",
"auth_error_base_failed_to_create_session": "Gagal membuat sesi",
"auth_error_base_failed_to_update_user": "Gagal memperbarui pengguna",
"auth_error_base_failed_to_get_session": "Gagal mengambil sesi",
"auth_error_base_invalid_password": "Password tidak valid",
"auth_error_base_invalid_email": "Email tidak valid",
"auth_error_base_invalid_email_or_password": "Email atau password tidak valid",
"auth_error_base_social_account_already_linked": "Akun sosial sudah tertaut",
"auth_error_base_provider_not_found": "Provider tidak ditemukan",
"auth_error_base_invalid_token": "Token tidak valid",
"auth_error_base_id_token_not_supported": "id_token tidak didukung",
"auth_error_base_failed_to_get_user_info": "Gagal mengambil info pengguna",
"auth_error_base_user_email_not_found": "Email pengguna tidak ditemukan",
"auth_error_base_email_not_verified": "Email belum terverifikasi",
"auth_error_base_password_too_short": "Password terlalu pendek",
"auth_error_base_password_too_long": "Password terlalu panjang",
"auth_error_base_user_already_exists": "Pengguna sudah ada.",
"auth_error_base_user_already_exists_use_another_email": "Pengguna sudah ada. Gunakan email lain.",
"auth_error_base_email_can_not_be_updated": "Email tidak dapat diperbarui",
"auth_error_base_credential_account_not_found": "Akun kredensial tidak ditemukan",
"auth_error_base_session_expired": "Sesi kedaluwarsa. Silakan autentikasi ulang untuk melakukan tindakan ini.",
"auth_error_base_failed_to_unlink_last_account": "Anda tidak dapat melepas tautan akun terakhir Anda",
"auth_error_base_account_not_found": "Akun tidak ditemukan",
"auth_error_base_user_already_has_password": "Pengguna sudah memiliki password. Gunakan itu untuk menghapus akun.",
"auth_error_base_cross_site_navigation_login_blocked": "Login lintas-situs diblokir. Permintaan ini terindikasi sebagai serangan CSRF.",
"auth_error_base_verification_email_not_enabled": "Email verifikasi belum diaktifkan",
"auth_error_base_email_already_verified": "Email sudah terverifikasi",
"auth_error_base_email_mismatch": "Email tidak cocok",
"auth_error_base_session_not_fresh": "Sesi tidak lagi valid untuk aksi ini",
"auth_error_base_linked_account_already_exists": "Akun tertaut sudah ada",
"auth_error_base_invalid_origin": "Origin tidak valid",
"auth_error_base_invalid_callback_url": "callbackURL tidak valid",
"auth_error_base_invalid_redirect_url": "redirectURL tidak valid",
"auth_error_base_invalid_error_callback_url": "errorCallbackURL tidak valid",
"auth_error_base_invalid_new_user_callback_url": "newUserCallbackURL tidak valid",
"auth_error_base_missing_or_null_origin": "Origin hilang atau null",
"auth_error_base_callback_url_required": "callbackURL wajib diisi",
"auth_error_base_failed_to_create_verification": "Tidak dapat membuat verifikasi",
"auth_error_base_field_not_allowed": "Field tidak diizinkan untuk diatur",
"auth_error_base_async_validation_not_supported": "Validasi async tidak didukung",
"auth_error_base_validation_error": "Kesalahan validasi",
"auth_error_base_missing_field": "Field wajib diisi"
}
Loading