chore(auth): redirect sign-in/sign-up to app.cameleer.io
Both auth flows now navigate to the app domain rather than the auth.cameleer.io subdomain: PUBLIC_AUTH_SIGNIN_URL → https://app.cameleer.io/sign-in PUBLIC_AUTH_SIGNUP_URL → https://app.cameleer.io/sign-in?first_screen=register Updated: - .env.example (the canonical reference values) - OPERATOR-CHECKLIST.md (deploy-time secret values) - src/config/auth.test.ts (test fixtures) - src/middleware.ts (CSP-comment about <a> navigation target) - src/pages/privacy.astro (visitor-facing external-links section in §6 of the privacy policy) The auth.ts validator stays strict-https — the new URLs are still absolute https URLs, just on a different host. Logto itself may still run at auth.cameleer.io as the OIDC backend; only the visitor-facing /sign-in entry point moved. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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'",
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user