Author SHA1 Message Date
Admin 35d6314ce8 Point the IDE dev settings at the shared Infrastructure stack
CI/CD / BackEnd (push) Successful in 1m18s
CI/CD / WebPlayer (push) Successful in 41s
CI/CD / Deploy (push) Skipped
appsettings.Development.json still targeted Musify's old own Postgres
(port 59000) with the superuser, the default RabbitMQ vhost as guest and
the admin S3 keys. They now use Infrastructure's dev ports and Musify's
own dev credentials (role, vhost and S3 identity), the same defaults as
deploy/.env.example.

The API's non-secret Zitadel settings (issuer, metadata, endpoints) move
there too, so user-secrets only need the per-machine client id.
2026-09-24 17:11:50 +02:00
Admin f99f74f4a1 Use Musify's own credentials on the shared services
Infrastructure now gives each project its own Postgres role, RabbitMQ
user and vhost, S3 identity limited to its bucket and a Zitadel service
account that owns only its project, instead of handing out the admin
accounts.

- Postgres, RabbitMQ and S3 settings default to Musify's own (musify).
- MassTransit connects to the `musify` vhost (RABBITMQ_VHOST, taken from
  the address path); RABBITMQ_DEFAULT_USER/PASS become RABBITMQ_USER and
  RABBITMQ_PASSWORD.
- zitadel-init mounts ZITADEL_PAT_DIR (Infrastructure's
  zitadel/.output/musify) and uses that PAT. It no longer creates the
  Zitadel project, which that account can't do: Infrastructure does.
2026-09-24 17:05:23 +02:00
Admin f2765e2e7b Call the API by container name from the web player
In prod every project shares infra-net, where the `api` service name
can resolve to another project's container as well.
2026-09-24 15:06:25 +02:00
Admin 7b8f3f1222 Use a reverse-domain redirect scheme for the native app
- Android/iOS now return to cat.ikerdemo.musify:/callback instead of
  musify://callback: a private-use scheme based on a domain we own
  (RFC 8252 section 7.1), so another app is unlikely to claim it.
- The Musify Native app no longer needs devMode: loopback and custom
  scheme redirects are both valid for a native app.
- zitadel-init also updates an existing app's config, so the change
  reaches instances created before it (the client id is kept).
- iOS takes the scheme from everything before the first ':', since the
  new URI has a single slash.
2026-09-24 15:06:25 +02:00
Admin c33b0e8bfd Stop disabling Zitadel Login V2 from zitadel-init
The shared Infrastructure stack now runs the Login V2 container, and
turning the feature off here would switch every project on the shared
instance back to the old login.
2026-09-24 15:06:25 +02:00
14 changed files with 121 additions and 88 deletions
@@ -6,21 +6,26 @@
}
},
"Authentication": {
"MetadataAddress": "http://host.docker.internal:8080/.well-known/openid-configuration",
"IssuerAddress": "http://host.docker.internal:8080",
"AuthorizationEndpoint": "http://host.docker.internal:8080/oauth/v2/authorize?prompt=login",
"TokenEndpoint": "http://host.docker.internal:8080/oauth/v2/token",
"ScalarRedirectUri": "http://localhost:5111/scalar/",
"Scopes": [ "openid", "profile", "email" ],
"RequireHttpsMetadata": false
},
"Database": {
"ConnectionString": "Host=localhost;Port=59000;Database=musify_db;Username=postgres;Password=postgres"
"ConnectionString": "Host=localhost;Port=5432;Database=musify_db;Username=musify;Password=musify"
},
"MassTransit": {
"Address": "rabbitmq://localhost:5672",
"Username": "guest",
"Password": "guest"
"Address": "rabbitmq://localhost:5672/musify",
"Username": "musify",
"Password": "musify"
},
"InfrastructureStorage": {
"Address": "http://localhost:8333",
"AccessKey": "admin_access_key",
"SecretAccessKey": "admin_secret_key",
"AccessKey": "musify_access_key",
"SecretAccessKey": "musify_secret_key",
"ForcePathStyle": true,
"UseHttp": true
},
@@ -6,20 +6,20 @@
}
},
"Database": {
"ConnectionString": "Host=localhost;Port=59000;Database=musify_db;Username=postgres;Password=postgres"
"ConnectionString": "Host=localhost;Port=5432;Database=musify_db;Username=musify;Password=musify"
},
"MassTransit": {
"Address": "rabbitmq://localhost:5672",
"Username": "guest",
"Password": "guest"
"Address": "rabbitmq://localhost:5672/musify",
"Username": "musify",
"Password": "musify"
},
"OpenTelemetry": {
"OtlpEndpoint": "http://localhost:4317"
},
"InfrastructureStorage": {
"Address": "http://localhost:8333",
"AccessKey": "admin_access_key",
"SecretAccessKey": "admin_secret_key",
"AccessKey": "musify_access_key",
"SecretAccessKey": "musify_secret_key",
"ForcePathStyle": true,
"UseHttp": true
},
+21 -15
View File
@@ -5,8 +5,10 @@
#
# Requires ../../../Infrastructure running first (Postgres, Zitadel,
# SeaweedFS, RabbitMQ, Jaeger). See ../../../Infrastructure/README.md.
# Everything marked "must match Infrastructure's .env" below has to be the
# same value as the one in that file.
# Musify has its own credentials on every shared service (role, vhost, S3
# identity, Zitadel service account), provisioned by Infrastructure. Every
# value marked "= Infrastructure's MUSIFY_*" below has to be the same as that
# setting in Infrastructure's .env.
DOTNET_ENVIRONMENT=Development
@@ -21,17 +23,18 @@ PUBLIC_STREAM_URL=http://localhost:8081
PUBLIC_AUTH_URL=http://host.docker.internal:8080
# --- PostgreSQL (Infrastructure's shared instance, Musify's own database) ---
# POSTGRES_USER / POSTGRES_PASSWORD must match Infrastructure's .env.
# Musify's own role, owner of musify_db only.
POSTGRES_HOST=host.docker.internal
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
POSTGRES_USER=musify
# = Infrastructure's MUSIFY_DB_PASSWORD
POSTGRES_PASSWORD=musify
POSTGRES_DB=musify_db
# --- Zitadel (OIDC, shared instance) -----------------------------------------
# Path to the admin service-account PAT Infrastructure's Zitadel writes on
# init. Adjust if your Infrastructure checkout lives somewhere else relative
# to this one.
ZITADEL_ADMIN_PAT_DIR=../../../Infrastructure/zitadel/.output
# Folder with Musify's own Zitadel PAT (a service account that owns the
# Musify project only), written by Infrastructure's `provision`. Adjust if
# your Infrastructure checkout lives somewhere else relative to this one.
ZITADEL_PAT_DIR=../../../Infrastructure/zitadel/.output/musify
# Allows the OIDC apps this project creates to use plain-http redirect URIs
# (dev only).
ZITADEL_DEV_MODE=true
@@ -43,16 +46,19 @@ WEB_CLIENT_ID=
WEB_CLIENT_SECRET=
# --- RabbitMQ (shared instance, MassTransit) --------------------------------
# Must match Infrastructure's .env.
# Musify's own user, only on its own vhost.
RABBITMQ_HOST=host.docker.internal
RABBITMQ_DEFAULT_USER=guest
RABBITMQ_DEFAULT_PASS=guest
RABBITMQ_VHOST=musify
RABBITMQ_USER=musify
# = Infrastructure's MUSIFY_RABBITMQ_PASSWORD
RABBITMQ_PASSWORD=musify
# --- SeaweedFS (S3, shared instance, Musify's own bucket) --------------------
# S3_ACCESS_KEY / S3_SECRET_KEY must match Infrastructure's .env.
# Musify's own identity, limited to S3_BUCKET.
SEAWEEDFS_HOST=host.docker.internal
S3_ACCESS_KEY=admin_access_key
S3_SECRET_KEY=admin_secret_key
# = Infrastructure's MUSIFY_S3_ACCESS_KEY / MUSIFY_S3_SECRET_KEY
S3_ACCESS_KEY=musify_access_key
S3_SECRET_KEY=musify_secret_key
S3_BUCKET=webapi-storage
S3_PUBLIC_URL=http://host.docker.internal:8333
S3_INTERNAL_URL=http://host.docker.internal:8333
+10 -5
View File
@@ -28,14 +28,16 @@ PUBLIC_AUTH_URL=https://auth.example.com
S3_PUBLIC_URL=https://s3.example.com
# --- PostgreSQL (Infrastructure's shared instance, Musify's own database) ---
# POSTGRES_USER / POSTGRES_PASSWORD must match Infrastructure's .env.prod.
# Musify's own role, owner of musify_db only.
POSTGRES_HOST=postgres
POSTGRES_USER=postgres
POSTGRES_USER=musify
# = Infrastructure's MUSIFY_DB_PASSWORD
POSTGRES_PASSWORD=CHANGE_ME
POSTGRES_DB=musify_db
# --- Zitadel (OIDC, shared instance) -----------------------------------------
ZITADEL_ADMIN_PAT_DIR=../../../Infrastructure/zitadel/.output
# Folder with Musify's own Zitadel PAT, written by Infrastructure's `provision`.
ZITADEL_PAT_DIR=../../../Infrastructure/zitadel/.output/musify
ZITADEL_DEV_MODE=false
# OIDC clients: the zitadel-init one-shot service (see compose.yml) fills
@@ -46,11 +48,14 @@ WEB_CLIENT_SECRET=
# --- RabbitMQ (shared instance, MassTransit) --------------------------------
RABBITMQ_HOST=rabbitmq
RABBITMQ_DEFAULT_USER=musify
RABBITMQ_DEFAULT_PASS=CHANGE_ME
RABBITMQ_VHOST=musify
RABBITMQ_USER=musify
# = Infrastructure's MUSIFY_RABBITMQ_PASSWORD
RABBITMQ_PASSWORD=CHANGE_ME
# --- SeaweedFS (S3, shared instance, Musify's own bucket) --------------------
SEAWEEDFS_HOST=seaweedfs
# = Infrastructure's MUSIFY_S3_ACCESS_KEY / MUSIFY_S3_SECRET_KEY
S3_ACCESS_KEY=CHANGE_ME
S3_SECRET_KEY=CHANGE_ME
S3_BUCKET=webapi-storage
+11 -7
View File
@@ -68,13 +68,17 @@ dotnet run --project backend/Hosts/Musify.Worker # background
npm --prefix web-player run dev # :5173
```
When running the apps from the IDE, `Musify.Api` reads `Authentication:*`
from user-secrets and `web-player` reads its own `web-player/.env`, neither
looks at `deploy/.env`. Copy the four values `zitadel-init` wrote
(`AUTH_CLIENT_ID`, `WEB_CLIENT_ID`, `WEB_CLIENT_SECRET`, `NATIVE_CLIENT_ID`)
into `dotnet user-secrets set Authentication:ClientId <value> --project
backend/Hosts/Musify.Api` and into `web-player/.env` once. They stay valid
until Infrastructure's Zitadel volume is wiped.
When running the apps from the IDE, neither the .NET hosts nor `web-player`
look at `deploy/.env`. The hosts' `appsettings.Development.json` already
point at Infrastructure's dev stack with Musify's own dev credentials (the
defaults of its `MUSIFY_*` settings) and at the shared Zitadel. Only the
OIDC client ids are per-machine: copy the values `zitadel-init` wrote once,
`AUTH_CLIENT_ID` into both `Authentication:ClientId` and
`Authentication:AudienceAddress` (`dotnet user-secrets set <key> <value>
--project backend/Hosts/Musify.Api`), `WEB_CLIENT_ID` and
`WEB_CLIENT_SECRET` into `web-player/.env`, and `NATIVE_CLIENT_ID` into
`mobile/gradle.properties`. They stay valid until Infrastructure's Zitadel
volume is wiped.
### Ports
+46 -37
View File
@@ -26,9 +26,10 @@
# already do.
x-backend-env: &backend-env
Database__ConnectionString: "Host=${POSTGRES_HOST};Port=5432;Database=${POSTGRES_DB};Username=${POSTGRES_USER};Password=${POSTGRES_PASSWORD}"
MassTransit__Address: "rabbitmq://${RABBITMQ_HOST}:5672"
MassTransit__Username: ${RABBITMQ_DEFAULT_USER}
MassTransit__Password: ${RABBITMQ_DEFAULT_PASS}
# Musify's own vhost and user on the shared broker (the path is the vhost).
MassTransit__Address: "rabbitmq://${RABBITMQ_HOST}:5672/${RABBITMQ_VHOST}"
MassTransit__Username: ${RABBITMQ_USER}
MassTransit__Password: ${RABBITMQ_PASSWORD}
# The public S3 URL, not the internal one: the same address is used to sign
# the presigned upload/download URLs the browser has to be able to reach.
InfrastructureStorage__Address: ${S3_PUBLIC_URL}
@@ -135,7 +136,7 @@ services:
PUBLIC_WEB_URL: ${PUBLIC_WEB_URL}
ZITADEL_DEV_MODE: ${ZITADEL_DEV_MODE:-false}
volumes:
- ${ZITADEL_ADMIN_PAT_DIR}:/output:ro
- ${ZITADEL_PAT_DIR}:/output:ro
entrypoint: ["/bin/sh", "-c"]
command:
- |
@@ -163,13 +164,15 @@ services:
fi
}
echo "==> waiting for the Zitadel service-account PAT"
# Musify's own service account, owner of the Musify project only
# (created by Infrastructure's `provision`, see its README).
echo "==> waiting for Musify's Zitadel PAT"
deadline=$$(( $$(date +%s) + 180 ))
while [ ! -f /output/admin-sa.pat ]; do
[ "$$(date +%s)" -lt "$$deadline" ] || { echo "PAT not found under /output. Is Infrastructure's Zitadel running? Check 'docker logs infra-zitadel'." >&2; exit 1; }
while [ ! -s /output/zitadel.pat ]; do
[ "$$(date +%s)" -lt "$$deadline" ] || { echo "no zitadel.pat in ZITADEL_PAT_DIR. Is Infrastructure up, and does ZITADEL_PAT_DIR point at its zitadel/.output/musify? Check 'docker logs infra-provision'." >&2; exit 1; }
sleep 3
done
PAT=$$(tr -d '\r\n' </output/admin-sa.pat)
PAT=$$(tr -d '\r\n' </output/zitadel.pat)
MGMT="$$PUBLIC_AUTH_URL/management/v1"
echo "==> waiting for Zitadel to answer at $$PUBLIC_AUTH_URL"
@@ -188,21 +191,13 @@ services:
}
name_query() { printf '{"queries":[{"nameQuery":{"name":"%s","method":"TEXT_QUERY_METHOD_EQUALS"}}]}' "$$1"; }
# `latest` images require the standalone Login UI v2 container, which
# this stack does not run; opting out keeps the built-in /ui/login.
curl -fsS -X PUT "$$PUBLIC_AUTH_URL/v2/features/instance" \
-H "Authorization: Bearer $$PAT" -H "Content-Type: application/json" \
-d '{"loginV2":{"required":false}}' >/dev/null 2>&1 || echo "warning: could not disable Login UI v2"
echo "==> project"
PROJECT_NAME=Musify
# Created by Infrastructure's `provision`: this account can't create
# projects, only manage this one.
PROJECT_ID=$$(api_call POST /projects/_search "$$(name_query "$$PROJECT_NAME")" | jq -r --arg n "$$PROJECT_NAME" '.result // [] | map(select(.name==$$n)) | first | .id // empty')
if [ -z "$$PROJECT_ID" ]; then
PROJECT_ID=$$(api_call POST /projects "$$(printf '{"name":"%s"}' "$$PROJECT_NAME")" | jq -r '.id')
echo " created (id $$PROJECT_ID)"
else
echo " reused (id $$PROJECT_ID)"
fi
[ -n "$$PROJECT_ID" ] || { echo "Zitadel project $$PROJECT_NAME not found. Is musify in Infrastructure's x-projects?" >&2; exit 1; }
echo " found (id $$PROJECT_ID)"
find_app() { api_call POST "/projects/$$PROJECT_ID/apps/_search" "$$(name_query "$$1")" | jq -r --arg n "$$1" '.result // [] | map(select(.name==$$n)) | first | .id // empty'; }
@@ -265,27 +260,38 @@ services:
fi
echo "==> native app (KMP client: desktop loopback + mobile custom scheme)"
# Loopback (desktop) and a reverse-domain custom scheme (Android/iOS,
# RFC 8252 §7.1) are both valid for a NATIVE app without devMode, so
# dev and prod share the same config. Unlike the other apps, an
# existing one is also updated, so a change here reaches instances
# created before it.
NATIVE_APP_NAME="Musify Native"
NATIVE_CONFIG='{
"redirectUris": ["http://127.0.0.1:17453/callback", "cat.ikerdemo.musify:/callback"],
"postLogoutRedirectUris": ["http://127.0.0.1:17453/logout", "cat.ikerdemo.musify:/logout"],
"responseTypes": ["OIDC_RESPONSE_TYPE_CODE"],
"grantTypes": ["OIDC_GRANT_TYPE_AUTHORIZATION_CODE", "OIDC_GRANT_TYPE_REFRESH_TOKEN"],
"appType": "OIDC_APP_TYPE_NATIVE",
"authMethodType": "OIDC_AUTH_METHOD_TYPE_NONE",
"devMode": false,
"accessTokenType": "OIDC_TOKEN_TYPE_JWT",
"accessTokenRoleAssertion": true,
"idTokenRoleAssertion": true,
"idTokenUserinfoAssertion": true
}'
NATIVE_APP_ID=$$(find_app "$$NATIVE_APP_NAME")
if [ -n "$$NATIVE_APP_ID" ]; then
NATIVE_CLIENT_ID=$$(api_call GET "/projects/$$PROJECT_ID/apps/$$NATIVE_APP_ID" | jq -r '.app.oidcConfig.clientId')
echo " reused (clientId $$NATIVE_CLIENT_ID)"
# Zitadel answers 400 "No changes" when the config already matches.
RESULT=$$(curl -sS -X PUT "$$MGMT/projects/$$PROJECT_ID/apps/$$NATIVE_APP_ID/oidc_config" \
-H "Authorization: Bearer $$PAT" -H "Content-Type: application/json" -d "$$NATIVE_CONFIG")
case "$$RESULT" in
*'"details"'*'"changeDate"'*) echo " reused (clientId $$NATIVE_CLIENT_ID, config updated)" ;;
*COMMAND-1m88i*) echo " reused (clientId $$NATIVE_CLIENT_ID)" ;;
*) echo "could not update the native app config: $$RESULT" >&2; exit 1 ;;
esac
else
NATIVE_CLIENT_ID=$$(api_call POST "/projects/$$PROJECT_ID/apps/oidc" '{
"name": "Musify Native",
"redirectUris": ["http://127.0.0.1:17453/callback", "musify://callback"],
"postLogoutRedirectUris": ["http://127.0.0.1:17453/logout", "musify://logout"],
"responseTypes": ["OIDC_RESPONSE_TYPE_CODE"],
"grantTypes": ["OIDC_GRANT_TYPE_AUTHORIZATION_CODE", "OIDC_GRANT_TYPE_REFRESH_TOKEN"],
"appType": "OIDC_APP_TYPE_NATIVE",
"authMethodType": "OIDC_AUTH_METHOD_TYPE_NONE",
"version": "OIDC_VERSION_1_0",
"devMode": true,
"accessTokenType": "OIDC_TOKEN_TYPE_JWT",
"accessTokenRoleAssertion": true,
"idTokenRoleAssertion": true,
"idTokenUserinfoAssertion": true
}' | jq -r '.clientId')
NATIVE_CLIENT_ID=$$(api_call POST "/projects/$$PROJECT_ID/apps/oidc" "$$(echo "$$NATIVE_CONFIG" | jq --arg name "$$NATIVE_APP_NAME" '. + {name: $$name, version: "OIDC_VERSION_1_0"}')" | jq -r '.clientId')
echo " created (clientId $$NATIVE_CLIENT_ID)"
fi
@@ -393,7 +399,10 @@ services:
AUTH_POST_LOGOUT_URI: "${PUBLIC_WEB_URL}/"
AUTH_ALLOW_INSECURE_HTTP: ${AUTH_ALLOW_INSECURE_HTTP}
SESSION_SECRET: ${SESSION_SECRET}
API_BASE_URL: http://api:5111
# The container name, not the `api` service name: in prod every project
# shares infra-net, where a generic name can resolve to another
# project's container.
API_BASE_URL: http://musify-api:5111
# Presigned PUTs are signed for the public S3 host; the container sends
# them straight to SeaweedFS instead of going back out through the edge.
S3_UPLOAD_ENDPOINT: ${S3_INTERNAL_URL}
+3 -3
View File
@@ -54,7 +54,7 @@ al patrón que ya usa Scalar para probar la API — ver `deploy/compose.yml` (`z
Redirect URIs registrados:
- Desktop: `http://127.0.0.1:17453/callback` (RFC 8252, loopback con puerto fijo).
- Android / iOS: `musify://callback` (custom scheme).
- Android / iOS: `cat.ikerdemo.musify:/callback` (custom scheme con dominio invertido, RFC 8252 §7.1).
`mobile/gradle.properties` controla a qué backend apunta Android/Desktop:
@@ -125,13 +125,13 @@ Kotlin/Native (`iosMain/`) sigue las APIs estándar de interop (`AVFoundation`,
1. Genera un proyecto Xcode con el asistente "Kotlin Multiplatform" de Android Studio, o crea uno vacío
e importa `composeApp` como framework (`./gradlew :composeApp:embedAndSignAppleFrameworkForXcode`).
2. Registra el scheme `musify` en `Info.plist`:
2. Registra el scheme `cat.ikerdemo.musify` en `Info.plist`:
```xml
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array><string>musify</string></array>
<array><string>cat.ikerdemo.musify</string></array>
</dict>
</array>
```
@@ -18,7 +18,9 @@
</intent-filter>
</activity>
<!-- Catches the `musify://callback` redirect Zitadel sends after login/register.
<!-- Catches the `cat.ikerdemo.musify:/callback` redirect Zitadel sends after
login/register. The URI has no host, so the filter matches on the scheme
alone.
See BrowserAuthLauncher.android.kt and AppConfig.nativeRedirectUri. -->
<activity
android:name=".auth.AuthCallbackActivity"
@@ -29,7 +31,7 @@
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="musify" android:host="callback" />
<data android:scheme="cat.ikerdemo.musify" />
</intent-filter>
</activity>
</application>
@@ -9,5 +9,5 @@ actual object AppConfig {
// Matches the AuthCallbackActivity intent-filter in AndroidManifest.xml and
// the redirect URI registered for the "Musify Native" app in Zitadel.
actual val nativeRedirectUri: String = "musify://callback"
actual val nativeRedirectUri: String = "cat.ikerdemo.musify:/callback"
}
@@ -5,7 +5,7 @@ import android.content.Intent
import android.os.Bundle
/**
* Declared in AndroidManifest.xml with an intent-filter for `musify://callback`.
* Declared in AndroidManifest.xml with an intent-filter for `cat.ikerdemo.musify:/callback`.
* Chrome Custom Tabs redirects here once Zitadel finishes the login/register
* flow; this activity just forwards the URL to [AndroidAuthBridge] and closes.
*/
@@ -9,7 +9,8 @@ package com.musify.app
* `nativeRedirectUri` is the OAuth redirect registered for the "Musify
* Native" Zitadel app (see deploy/compose.yml, zitadel-init): a fixed
* loopback port on Desktop (RFC 8252 native-app loopback flow), and the
* `musify://callback` custom scheme on Android/iOS.
* `cat.ikerdemo.musify:/callback` reverse-domain custom scheme on Android/iOS
* (RFC 8252 §7.1: based on a domain we own, so no other app plausibly claims it).
*/
expect object AppConfig {
val apiBaseUrl: String
@@ -12,7 +12,7 @@ package com.musify.app.auth
* - Desktop: a one-shot loopback HTTP server on the fixed port registered in
* Zitadel (RFC 8252 §7.3), opened via the OS default browser.
* - Android: Chrome Custom Tabs; the redirect is caught by
* [AuthCallbackActivity] via the `musify://callback` intent filter.
* [AuthCallbackActivity] via the `cat.ikerdemo.musify:/callback` intent filter.
* - iOS: `ASWebAuthenticationSession`, which owns the whole round trip.
*/
expect class BrowserAuthLauncher() {
@@ -12,5 +12,5 @@ actual object AppConfig {
// Matches CFBundleURLTypes in Info.plist and the redirect URI registered
// for the "Musify Native" app in Zitadel.
actual val nativeRedirectUri: String = "musify://callback"
actual val nativeRedirectUri: String = "cat.ikerdemo.musify:/callback"
}
@@ -23,14 +23,15 @@ private class PresentationContextProvider : NSObject(), ASWebAuthenticationPrese
/**
* `ASWebAuthenticationSession` owns the whole round trip: it opens a system
* browser sheet at [authorizeUrl] (Zitadel's hosted login, with its
* "Regístrate" link) and intercepts the `musify://` redirect itself, no app
* "Regístrate" link) and intercepts the `cat.ikerdemo.musify:` redirect itself, no app
* delegate / `onOpenURL` wiring required.
*/
@OptIn(ExperimentalForeignApi::class)
actual class BrowserAuthLauncher actual constructor() {
actual suspend fun launch(authorizeUrl: String, redirectUri: String): String =
suspendCancellableCoroutine { continuation ->
val scheme = redirectUri.substringBefore("://")
// `scheme:/path` (RFC 8252 private-use scheme): everything before the first ':'.
val scheme = redirectUri.substringBefore(":")
val contextProvider = PresentationContextProvider()
val session = ASWebAuthenticationSession(