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.
# No tokens, no cookies, no XHR — these are plain hyperlinks.
PUBLIC_AUTH_SIGNIN_URL=https://auth.cameleer.io/sign-in
PUBLIC_AUTH_SIGNUP_URL=https://auth.cameleer.io/sign-in?first_screen=register
PUBLIC_AUTH_SIGNIN_URL=https://app.cameleer.io/sign-in
PUBLIC_AUTH_SIGNUP_URL=https://app.cameleer.io/sign-in?first_screen=register
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_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`) |
| `PUBLIC_AUTH_SIGNIN_URL` | secret | `https://auth.cameleer.io/sign-in` |
| `PUBLIC_AUTH_SIGNUP_URL` | secret | `https://auth.cameleer.io/sign-in?first_screen=register` |
| `PUBLIC_AUTH_SIGNIN_URL` | secret | `https://app.cameleer.io/sign-in` |
| `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) |
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', () => {
it('returns both URLs and sales email from env', () => {
const cfg = resolveAuthConfig({
PUBLIC_AUTH_SIGNIN_URL: 'https://auth.cameleer.io/sign-in',
PUBLIC_AUTH_SIGNUP_URL: 'https://auth.cameleer.io/sign-in?first_screen=register',
PUBLIC_AUTH_SIGNIN_URL: 'https://app.cameleer.io/sign-in',
PUBLIC_AUTH_SIGNUP_URL: 'https://app.cameleer.io/sign-in?first_screen=register',
PUBLIC_SALES_EMAIL: 'sales@cameleer.io',
});
expect(cfg.signInUrl).toBe('https://auth.cameleer.io/sign-in');
expect(cfg.signUpUrl).toBe('https://auth.cameleer.io/sign-in?first_screen=register');
expect(cfg.signInUrl).toBe('https://app.cameleer.io/sign-in');
expect(cfg.signUpUrl).toBe('https://app.cameleer.io/sign-in?first_screen=register');
expect(cfg.salesEmail).toBe('sales@cameleer.io');
});
it('throws if PUBLIC_AUTH_SIGNIN_URL is missing', () => {
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',
})).toThrow(/PUBLIC_AUTH_SIGNIN_URL/);
});
it('throws if a URL is not https', () => {
expect(() => resolveAuthConfig({
PUBLIC_AUTH_SIGNIN_URL: 'http://auth.cameleer.io/sign-in',
PUBLIC_AUTH_SIGNUP_URL: 'https://auth.cameleer.io/sign-in?first_screen=register',
PUBLIC_AUTH_SIGNIN_URL: 'http://app.cameleer.io/sign-in',
PUBLIC_AUTH_SIGNUP_URL: 'https://app.cameleer.io/sign-in?first_screen=register',
PUBLIC_SALES_EMAIL: 'sales@cameleer.io',
})).toThrow(/must be https/);
});
it('throws if sales email is not a valid mailto target', () => {
expect(() => resolveAuthConfig({
PUBLIC_AUTH_SIGNIN_URL: 'https://auth.cameleer.io/sign-in',
PUBLIC_AUTH_SIGNUP_URL: 'https://auth.cameleer.io/sign-in?first_screen=register',
PUBLIC_AUTH_SIGNIN_URL: 'https://app.cameleer.io/sign-in',
PUBLIC_AUTH_SIGNUP_URL: 'https://app.cameleer.io/sign-in?first_screen=register',
PUBLIC_SALES_EMAIL: 'not-an-email',
})).toThrow(/PUBLIC_SALES_EMAIL/);
});
it('throws if PUBLIC_AUTH_SIGNUP_URL is missing', () => {
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',
})).toThrow(/PUBLIC_AUTH_SIGNUP_URL/);
});
it('throws if PUBLIC_AUTH_SIGNUP_URL is not https', () => {
expect(() => resolveAuthConfig({
PUBLIC_AUTH_SIGNIN_URL: 'https://auth.cameleer.io/sign-in',
PUBLIC_AUTH_SIGNUP_URL: 'http://auth.cameleer.io/sign-in?first_screen=register',
PUBLIC_AUTH_SIGNIN_URL: 'https://app.cameleer.io/sign-in',
PUBLIC_AUTH_SIGNUP_URL: 'http://app.cameleer.io/sign-in?first_screen=register',
PUBLIC_SALES_EMAIL: 'sales@cameleer.io',
})).toThrow(/must be https/);
});
it('exposes signUpUrl distinct from signInUrl', () => {
const cfg = resolveAuthConfig({
PUBLIC_AUTH_SIGNIN_URL: 'https://auth.cameleer.io/sign-in',
PUBLIC_AUTH_SIGNUP_URL: 'https://auth.cameleer.io/sign-in?first_screen=register',
PUBLIC_AUTH_SIGNIN_URL: 'https://app.cameleer.io/sign-in',
PUBLIC_AUTH_SIGNUP_URL: 'https://app.cameleer.io/sign-in?first_screen=register',
PUBLIC_SALES_EMAIL: 'sales@cameleer.io',
});
expect(cfg.signUpUrl).not.toBe(cfg.signInUrl);

View File

@@ -20,7 +20,7 @@ export function buildSecurityHeaders(): Record<string, string> {
"connect-src 'self'",
"frame-ancestors 'none'",
"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.
"form-action 'none'",
"object-src 'none'",

View File

@@ -67,7 +67,7 @@ const lastUpdated = '2026-04-24';
<section class="mb-10">
<h2 class="text-lg font-bold text-text mb-3">6. External links</h2>
<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>
</section>