···4747 # Use smaller resources for review apps
4848 vmsize: shared-cpu-1x
4949 memory: 256
5050- # Set OAUTH_REDIRECT_BASE dynamically for OAuth redirects
5050+ # Set OAUTH_REDIRECT_BASE and APP_URL dynamically for OAuth redirects
5151 secrets: |
5252- OAUTH_REDIRECT_BASE=https://zzstoatzz-status-pr-${{ github.event.number }}.fly.dev5252+ OAUTH_REDIRECT_BASE=https://zzstoatzz-status-pr-${{ github.event.number }}.fly.dev
5353+ APP_URL=https://zzstoatzz-status-pr-${{ github.event.number }}.fly.dev
+1
fly.review.toml
···1111 ENABLE_FIREHOSE = "true"
1212 DEV_MODE = "true"
1313 # OAUTH_REDIRECT_BASE will be set dynamically by the workflow
1414+ # APP_URL will be set dynamically by the workflow
14151516[http_service]
1617 internal_port = 8080
+10-1
src/api/auth.rs
···6464 request: HttpRequest,
6565 params: web::Query<OAuthCallbackParams>,
6666 oauth_client: web::Data<OAuthClientType>,
6767+ config: web::Data<config::Config>,
6768 session: Session,
6869) -> HttpResponse {
6970 // Check if there's an OAuth error from BlueSky
···109110 match agent.did().await {
110111 Some(did) => {
111112 session.insert("did", did).unwrap();
112112- Redirect::to("/")
113113+ // Redirect back to main app domain after successful auth
114114+ let redirect_to = if config.uses_separate_auth_domain() {
115115+ config.app_url.clone()
116116+ } else {
117117+ "/".to_string()
118118+ };
119119+ Redirect::to(redirect_to)
113120 .see_other()
114121 .respond_to(&request)
115122 .map_into_boxed_body()
···137144/// Takes you to the login page
138145#[get("/login")]
139146pub async fn login() -> Result<impl Responder> {
147147+ // Don't redirect - just serve the login page
148148+ // The OAuth will use the correct redirect URL from config
140149 let html = LoginTemplate {
141150 title: "Log in",
142151 error: None,
+27-3
src/config.rs
···1414 /// Database URL (defaults to local SQLite)
1515 pub database_url: String,
16161717- /// OAuth redirect base URL
1717+ /// OAuth redirect base URL (auth domain)
1818 pub oauth_redirect_base: String,
1919+2020+ /// Main app URL (status domain)
2121+ pub app_url: String,
19222023 /// Server host
2124 pub server_host: String,
···3740}
38413942impl Config {
4343+ /// Check if we're using a separate auth domain
4444+ pub fn uses_separate_auth_domain(&self) -> bool {
4545+ self.oauth_redirect_base != self.app_url
4646+ }
4747+4048 /// Load configuration from environment variables with sensible defaults
4149 pub fn from_env() -> Result<Self, env::VarError> {
4250 // Admin DID is intentionally hardcoded as discussed
4351 let admin_did = "did:plc:xbtmt2zjwlrfegqvch7fboei".to_string();
44524545- Ok(Config {
5353+ let config = Config {
4654 admin_did,
4755 owner_handle: env::var("OWNER_HANDLE").unwrap_or_else(|_| "zzstoatzz.io".to_string()),
4856 database_url: env::var("DATABASE_URL")
4957 .unwrap_or_else(|_| "sqlite://./statusphere.sqlite3".to_string()),
5058 oauth_redirect_base: env::var("OAUTH_REDIRECT_BASE")
5159 .unwrap_or_else(|_| "http://localhost:8080".to_string()),
6060+ app_url: env::var("APP_URL").unwrap_or_else(|_| "http://localhost:8080".to_string()),
5261 server_host: env::var("SERVER_HOST").unwrap_or_else(|_| "127.0.0.1".to_string()),
5362 server_port: env::var("SERVER_PORT")
5463 .unwrap_or_else(|_| "8080".to_string())
···6574 .unwrap_or(false),
6675 // Default to static/emojis for local dev; override in prod to /data/emojis
6776 emoji_dir: env::var("EMOJI_DIR").unwrap_or_else(|_| "static/emojis".to_string()),
6868- })
7777+ };
7878+7979+ // Validate critical URLs at startup
8080+ if url::Url::parse(&config.oauth_redirect_base).is_err() {
8181+ log::error!(
8282+ "Invalid OAUTH_REDIRECT_BASE URL: {}",
8383+ config.oauth_redirect_base
8484+ );
8585+ panic!("Invalid OAUTH_REDIRECT_BASE URL configuration");
8686+ }
8787+ if url::Url::parse(&config.app_url).is_err() {
8888+ log::error!("Invalid APP_URL: {}", config.app_url);
8989+ panic!("Invalid APP_URL configuration");
9090+ }
9191+9292+ Ok(config)
6993 }
7094}