Merge pull request 'chore(auth): redirect sign-in/sign-up to app.cameleer.io' (#5) from relaunch-2026-04-25 into main
All checks were successful
ci / build-test (push) Successful in 4m17s

Reviewed-on: #5
This commit was merged in pull request #5.
This commit is contained in:
2026-04-25 09:33:23 +02:00
5 changed files with 20 additions and 20 deletions

View File

@@ -1,5 +1,5 @@
# Logto auth endpoints — the marketing site only performs <a href> navigations to these. # Logto auth endpoints — the marketing site only performs <a href> navigations to these.
# No tokens, no cookies, no XHR — these are plain hyperlinks. # No tokens, no cookies, no XHR — these are plain hyperlinks.
PUBLIC_AUTH_SIGNIN_URL=https://auth.cameleer.io/sign-in PUBLIC_AUTH_SIGNIN_URL=https://app.cameleer.io/sign-in
PUBLIC_AUTH_SIGNUP_URL=https://auth.cameleer.io/sign-in?first_screen=register PUBLIC_AUTH_SIGNUP_URL=https://app.cameleer.io/sign-in?first_screen=register
PUBLIC_SALES_EMAIL=sales@cameleer.io PUBLIC_SALES_EMAIL=sales@cameleer.io

View File

@@ -75,8 +75,8 @@ Add these under Repository settings → Actions → Secrets (or variables):
| `SFTP_PATH` | secret | Absolute path to the Apache vhost docroot configured in konsoleH (typically `/usr/www/users/<login>/public_html`). Mismatch → 404 on origin. | | `SFTP_PATH` | secret | Absolute path to the Apache vhost docroot configured in konsoleH (typically `/usr/www/users/<login>/public_html`). Mismatch → 404 on origin. |
| `SFTP_KEY` | secret | Contents of `~/.ssh/cameleer-website-deploy` (private key, PEM) | | `SFTP_KEY` | secret | Contents of `~/.ssh/cameleer-website-deploy` (private key, PEM) |
| `SFTP_KNOWN_HOSTS` | secret | Contents of `hetzner-known-hosts.txt` (captured via `ssh-keyscan`) | | `SFTP_KNOWN_HOSTS` | secret | Contents of `hetzner-known-hosts.txt` (captured via `ssh-keyscan`) |
| `PUBLIC_AUTH_SIGNIN_URL` | secret | `https://auth.cameleer.io/sign-in` | | `PUBLIC_AUTH_SIGNIN_URL` | secret | `https://app.cameleer.io/sign-in` |
| `PUBLIC_AUTH_SIGNUP_URL` | secret | `https://auth.cameleer.io/sign-in?first_screen=register` | | `PUBLIC_AUTH_SIGNUP_URL` | secret | `https://app.cameleer.io/sign-in?first_screen=register` |
| `PUBLIC_SALES_EMAIL` | secret | `sales@cameleer.io` (or whatever sales alias you set up) | | `PUBLIC_SALES_EMAIL` | secret | `sales@cameleer.io` (or whatever sales alias you set up) |
These three are not actually secret (they end up in the built HTML), but Gitea's These three are not actually secret (they end up in the built HTML), but Gitea's

View File

@@ -4,57 +4,57 @@ import { resolveAuthConfig } from './auth';
describe('resolveAuthConfig', () => { describe('resolveAuthConfig', () => {
it('returns both URLs and sales email from env', () => { it('returns both URLs and sales email from env', () => {
const cfg = resolveAuthConfig({ const cfg = resolveAuthConfig({
PUBLIC_AUTH_SIGNIN_URL: 'https://auth.cameleer.io/sign-in', PUBLIC_AUTH_SIGNIN_URL: 'https://app.cameleer.io/sign-in',
PUBLIC_AUTH_SIGNUP_URL: 'https://auth.cameleer.io/sign-in?first_screen=register', PUBLIC_AUTH_SIGNUP_URL: 'https://app.cameleer.io/sign-in?first_screen=register',
PUBLIC_SALES_EMAIL: 'sales@cameleer.io', PUBLIC_SALES_EMAIL: 'sales@cameleer.io',
}); });
expect(cfg.signInUrl).toBe('https://auth.cameleer.io/sign-in'); expect(cfg.signInUrl).toBe('https://app.cameleer.io/sign-in');
expect(cfg.signUpUrl).toBe('https://auth.cameleer.io/sign-in?first_screen=register'); expect(cfg.signUpUrl).toBe('https://app.cameleer.io/sign-in?first_screen=register');
expect(cfg.salesEmail).toBe('sales@cameleer.io'); expect(cfg.salesEmail).toBe('sales@cameleer.io');
}); });
it('throws if PUBLIC_AUTH_SIGNIN_URL is missing', () => { it('throws if PUBLIC_AUTH_SIGNIN_URL is missing', () => {
expect(() => resolveAuthConfig({ expect(() => resolveAuthConfig({
PUBLIC_AUTH_SIGNUP_URL: 'https://auth.cameleer.io/sign-in?first_screen=register', PUBLIC_AUTH_SIGNUP_URL: 'https://app.cameleer.io/sign-in?first_screen=register',
PUBLIC_SALES_EMAIL: 'sales@cameleer.io', PUBLIC_SALES_EMAIL: 'sales@cameleer.io',
})).toThrow(/PUBLIC_AUTH_SIGNIN_URL/); })).toThrow(/PUBLIC_AUTH_SIGNIN_URL/);
}); });
it('throws if a URL is not https', () => { it('throws if a URL is not https', () => {
expect(() => resolveAuthConfig({ expect(() => resolveAuthConfig({
PUBLIC_AUTH_SIGNIN_URL: 'http://auth.cameleer.io/sign-in', PUBLIC_AUTH_SIGNIN_URL: 'http://app.cameleer.io/sign-in',
PUBLIC_AUTH_SIGNUP_URL: 'https://auth.cameleer.io/sign-in?first_screen=register', PUBLIC_AUTH_SIGNUP_URL: 'https://app.cameleer.io/sign-in?first_screen=register',
PUBLIC_SALES_EMAIL: 'sales@cameleer.io', PUBLIC_SALES_EMAIL: 'sales@cameleer.io',
})).toThrow(/must be https/); })).toThrow(/must be https/);
}); });
it('throws if sales email is not a valid mailto target', () => { it('throws if sales email is not a valid mailto target', () => {
expect(() => resolveAuthConfig({ expect(() => resolveAuthConfig({
PUBLIC_AUTH_SIGNIN_URL: 'https://auth.cameleer.io/sign-in', PUBLIC_AUTH_SIGNIN_URL: 'https://app.cameleer.io/sign-in',
PUBLIC_AUTH_SIGNUP_URL: 'https://auth.cameleer.io/sign-in?first_screen=register', PUBLIC_AUTH_SIGNUP_URL: 'https://app.cameleer.io/sign-in?first_screen=register',
PUBLIC_SALES_EMAIL: 'not-an-email', PUBLIC_SALES_EMAIL: 'not-an-email',
})).toThrow(/PUBLIC_SALES_EMAIL/); })).toThrow(/PUBLIC_SALES_EMAIL/);
}); });
it('throws if PUBLIC_AUTH_SIGNUP_URL is missing', () => { it('throws if PUBLIC_AUTH_SIGNUP_URL is missing', () => {
expect(() => resolveAuthConfig({ expect(() => resolveAuthConfig({
PUBLIC_AUTH_SIGNIN_URL: 'https://auth.cameleer.io/sign-in', PUBLIC_AUTH_SIGNIN_URL: 'https://app.cameleer.io/sign-in',
PUBLIC_SALES_EMAIL: 'sales@cameleer.io', PUBLIC_SALES_EMAIL: 'sales@cameleer.io',
})).toThrow(/PUBLIC_AUTH_SIGNUP_URL/); })).toThrow(/PUBLIC_AUTH_SIGNUP_URL/);
}); });
it('throws if PUBLIC_AUTH_SIGNUP_URL is not https', () => { it('throws if PUBLIC_AUTH_SIGNUP_URL is not https', () => {
expect(() => resolveAuthConfig({ expect(() => resolveAuthConfig({
PUBLIC_AUTH_SIGNIN_URL: 'https://auth.cameleer.io/sign-in', PUBLIC_AUTH_SIGNIN_URL: 'https://app.cameleer.io/sign-in',
PUBLIC_AUTH_SIGNUP_URL: 'http://auth.cameleer.io/sign-in?first_screen=register', PUBLIC_AUTH_SIGNUP_URL: 'http://app.cameleer.io/sign-in?first_screen=register',
PUBLIC_SALES_EMAIL: 'sales@cameleer.io', PUBLIC_SALES_EMAIL: 'sales@cameleer.io',
})).toThrow(/must be https/); })).toThrow(/must be https/);
}); });
it('exposes signUpUrl distinct from signInUrl', () => { it('exposes signUpUrl distinct from signInUrl', () => {
const cfg = resolveAuthConfig({ const cfg = resolveAuthConfig({
PUBLIC_AUTH_SIGNIN_URL: 'https://auth.cameleer.io/sign-in', PUBLIC_AUTH_SIGNIN_URL: 'https://app.cameleer.io/sign-in',
PUBLIC_AUTH_SIGNUP_URL: 'https://auth.cameleer.io/sign-in?first_screen=register', PUBLIC_AUTH_SIGNUP_URL: 'https://app.cameleer.io/sign-in?first_screen=register',
PUBLIC_SALES_EMAIL: 'sales@cameleer.io', PUBLIC_SALES_EMAIL: 'sales@cameleer.io',
}); });
expect(cfg.signUpUrl).not.toBe(cfg.signInUrl); expect(cfg.signUpUrl).not.toBe(cfg.signInUrl);

View File

@@ -20,7 +20,7 @@ export function buildSecurityHeaders(): Record<string, string> {
"connect-src 'self'", "connect-src 'self'",
"frame-ancestors 'none'", "frame-ancestors 'none'",
"base-uri 'self'", "base-uri 'self'",
// No forms on this marketing site today (all auth redirects go to auth.cameleer.io // No forms on this marketing site today (all auth redirects go to app.cameleer.io
// as plain <a> navigations). If a future form is added, relax to 'self' or an allow-list. // as plain <a> navigations). If a future form is added, relax to 'self' or an allow-list.
"form-action 'none'", "form-action 'none'",
"object-src 'none'", "object-src 'none'",

View File

@@ -67,7 +67,7 @@ const lastUpdated = '2026-04-24';
<section class="mb-10"> <section class="mb-10">
<h2 class="text-lg font-bold text-text mb-3">6. External links</h2> <h2 class="text-lg font-bold text-text mb-3">6. External links</h2>
<p class="text-text-muted leading-relaxed"> <p class="text-text-muted leading-relaxed">
Sign-in and sign-up links on this site navigate you to <span class="font-mono text-accent">auth.cameleer.io</span> (Logto identity service) and subsequently <span class="font-mono text-accent">platform.cameleer.io</span>. Those services have their own privacy policies, which apply from the moment you arrive there. Sign-in and sign-up links on this site navigate you to <span class="font-mono text-accent">app.cameleer.io</span> (the Cameleer app, where authentication is handled by Logto). That service has its own privacy policy, which applies from the moment you arrive there.
</p> </p>
</section> </section>