From 668b7ab2337562d3dda8657843e13e408a1f056e Mon Sep 17 00:00:00 2001 From: Chris Bongers Date: Fri, 20 Mar 2026 11:12:24 +0200 Subject: [PATCH 1/2] fix: dynamic apple secret --- .infra/Pulumi.adhoc.yaml | 10 ++++++-- .infra/Pulumi.prod.yaml | 9 ++++++-- .infra/index.ts | 28 ++++++++++++++++++++++ src/betterAuth.ts | 50 +++++++++++++++++++++++++++++++++++++++- 4 files changed, 92 insertions(+), 5 deletions(-) diff --git a/.infra/Pulumi.adhoc.yaml b/.infra/Pulumi.adhoc.yaml index 597e9c0106..4ed92f5504 100644 --- a/.infra/Pulumi.adhoc.yaml +++ b/.infra/Pulumi.adhoc.yaml @@ -115,8 +115,6 @@ config: secure: v1:GhL76XX0Y/yzoYG8:6kARUQHVOxbIP/WXxH+2IX91ADp650ZrEG7H+6FXuakJuzhDHTpk+q37jUq1hzHzp8xNbNFlud4= appleClientId: secure: v1:pK4ks8kCNPZO/KJc:REutw/UeJumYLJWA8cPy5iL83yaVgOOZh9azOyo7zcyj - appleClientSecret: - secure: v1:8TOrQ25yzgkRxr2C:n6FB51tbRNAl5bujHFjsn6a7kxEWlirmhX4wj/iWGM5BSc7n+8AxQkxhiLhe6xE0aXvfTPMUurfvbRMBHfkzbrJi7sZkMX7ZsseUHZLbyphA6+L3VxwsVvehArNx5iVJ8MzZW/LxsXZ50AtUjuXzLvaJKQtImdU9MLSPKy/EEOClTBSIn0leHJuEuDI4l7uFUN845qb+R6buTvy26jzaBNxTM//zLvf+2LN+iIbCKXsm77NdRT76x+Zr3rBQSnEWMZJjo4SE3eW5PJ30ZD0XJxung1CcErhWtVeHOkOQjGs4p6OGO/5S3NXK+RS65qQnMPoYiYPhkKhcmd/2FJqruzV7J93FGyw5+LUJ/AqPKpdCRNi1gMS5RPg+SG6Q5YiqvcMWiNbKPCsLStL3bZ2DKyvfBJx9XFWWSDcCFfft facebookClientId: secure: v1:HK6y83jsUMlifn1s:lVkv/LDrS8XKQDZtux2UgCCDMU+nAq1W2b1PNngYWQ== facebookClientSecret: @@ -126,5 +124,13 @@ config: api:temporal: chain: '' key: '' + api:appleAuth: + teamId: + secure: v1:Ktskb12lh2HtPDRC:N+t3MbLzM274/SMsnN0cejzScaxF6I62sic= + keyId: + secure: v1:AeanAY9fDkWD2gSg:zleimS/qqmb3SCjeHTQ7Mp8QINMs/DcqFE0= + signingKey: + secure: v1:+2csKe943/E3qzYp:aOG0lDimtWcUCbASDNy9RpxsKnb6eV3JJ31xYtbs9NA3yeoDVBsMvLK0sba0r7lRgGg/SIqCHawAa/GyC4v3dW8rVfgbiAyvLEf0rdaXItVYM2HUimTK+bj+DQ6bLY9nol8JqaRaHfg7NicC+QHnCQogxsiUiS/U9GsXiBEWTQYBW50YSMMKBCzQV2OJU23434f6/EVL1m4scyKHL4wPUDVWi19lOhBAlWTutSXLWt113fafaaS44AIb64mspiThAeuQUAof1tSFn9dc+WgCyekQRQ7RGBcgoANubMthpU540cP3B9PU93TOnh6It1XOC3dYjsMTSWZjNFCXIeJs8+Y8NqbL/g1oOhFn3LrTtJ7c + api:image: api-image:tilt-0414354c2f7cfdc5 secretsprovider: gcpkms://projects/daily-ops/locations/global/keyRings/pulumi/cryptoKeys/adhoc-secrets encryptedkey: CiQAUBCtsAuGPHI7MeeWrccjf/ydEN57pW/AH+8NWsRvdPXM6e8SSABTzSDRfy71YqxOXL5RHxmOL5aU0fwHtiyHj4R5XRZbwtTUivxPk0qEIMtvbQSBCu7NE6CSd+uvaq5B/yz0DU8FWvGuYUzJXQ== diff --git a/.infra/Pulumi.prod.yaml b/.infra/Pulumi.prod.yaml index 77c8e861c6..61a6e70087 100644 --- a/.infra/Pulumi.prod.yaml +++ b/.infra/Pulumi.prod.yaml @@ -257,8 +257,6 @@ config: secure: AAABAEfjThwIBs0n3204XYFwZxshz0PuyY1bW1STItI8wzIb9z8WZcGcVpLHER0= facebookClientSecret: secure: AAABAPPqGlgKF6TZr2fIXHfVNcQEnRwrFfEScvb5Ogi+BEI9z9nSz47AF0KDM/dnSrV9ijKcRGloq3d3M34QAQ== - appleClientSecret: - secure: AAABAKJ1kXfmD4g3p/fZg5Gbid2scaPO4VQeK2VAWyZW/J54OvGGFuWg34X+YLyTPJbsyA6TsutCdSQsO9hRWXIzNFjZK9XZo4PmnCD5/MJbReYI6M3ayqc8M2bLBRaaDSj0/nrop+P9J3cMy/944b1F271lXOsCgNrHn4dkYLX0tkIuCnu86VSPaxJ9bRPRy83igX8w7P0gE5vKrnmLj29CSXk7iZGB1yikFPWddHLZUCxkX8gZ15kanvg6bvVdkrlotKdl8kV12iClSDc5gYDG/qsHsuHTixQ3vTmwf2R7svAAA3DeD6gJhXobRkbpNQMYMmwm2lwMzvwv394gMCKJ4/OsruktloyorRKIZP3jSrgs4XHT/buQC87H7n9WxGU/twKvzpbpkqi3ibdNhh9wKYVhTe/HCbZvv8rsCX+wbEeb appleClientId: secure: AAABABe5GePYDxcK+9QGqmQV7zE3Pv5wWtGkfFOV1MosH+KVFIfK6Is= googleIosClientId: @@ -274,3 +272,10 @@ config: gcp:project: devkit-prod gcp:region: us-central1 api:tag: 8a7bc7c1afab1d2bce67a6bc22e1be2662135cc4 + api:appleAuth: + teamId: + secure: AAABACONn3aOuJjNxn+VlHZL1YCA7swTqY6JzEGvnhcMtawnjot1qAOb + keyId: + secure: AAABAH16I8xQAJjTyasPQzLDqd3AAo7q4dMEBGHVbgJJVvSviQelQT09 + signingKey: + secure: AAABAGxRaqkffmtnKxeVFwStkBB2QUM1t5XtIOBJN0T/AcAJOac8oaovU/4H41JxFS5tnOOvWqN6HLvyejByOZ6UtxF6DqC27mvRiOKu863MKg7JrL3wPhHqEEgDJmdoGfqJw4Yady/CLRMqn58TCUwwNHRlcESng2RmwT9+BDXZywRFXfE5NKp41qLtLeOU6piAooQghHjVX6EQd8AG9Ffl2ehijRcwNnpgkQR8oz1V+Ilat6fvn9qQT1bjrBSE6oagdOxeLBdsVOmoa0H6ZbocsGmtvETEypK1uW62QD/Ar2PYtRSsFhvY9YiUf5qMxa6Kp1fJ4IdupoOKaAZY3yHlTFHXkU6pg0GGpfhktfaEgZaEgxcoknpQzPwEknjiAQ== diff --git a/.infra/index.ts b/.infra/index.ts index f6e8d52e94..c3edeffbec 100644 --- a/.infra/index.ts +++ b/.infra/index.ts @@ -145,6 +145,34 @@ const envVars: Record> = { redisPort: redis.port.apply((port) => port.toString()), }; +const appleAuth = config.getObject<{ + teamId: string; + keyId: string; + signingKey: string; +}>('appleAuth'); + +if (appleAuth) { + envVars.appleTeamId = appleAuth.teamId; + envVars.appleKeyId = appleAuth.keyId; + envVars.appleSigningKeyPath = '/opt/app/apple/AuthKey.p8'; + + vols.volumes.push({ + name: 'apple-auth', + secret: { secretName: 'apple-auth-secret' }, + }); + vols.volumeMounts.push({ + name: 'apple-auth', + mountPath: '/opt/app/apple', + }); + + additionalSecrets.push({ + name: 'apple-auth-secret', + data: { + 'AuthKey.p8': Buffer.from(appleAuth.signingKey).toString('base64'), + }, + }); +} + createSubscriptionsFromWorkers( name, isAdhocEnv, diff --git a/src/betterAuth.ts b/src/betterAuth.ts index 969b2374ea..3a84ae88e3 100644 --- a/src/betterAuth.ts +++ b/src/betterAuth.ts @@ -15,10 +15,55 @@ import { singleRedisClient } from './redis'; import { User } from './entity/user/User'; import { fetchOptions } from './http'; import { retryFetch } from './integrations/retry'; +import * as crypto from 'crypto'; +import * as fs from 'fs'; import { cookies, extractRootDomain } from './cookies'; const GOOGLE_CERTS_URL = 'https://www.googleapis.com/oauth2/v3/certs'; +const generateAppleClientSecret = (): string | undefined => { + const { + APPLE_CLIENT_ID, + APPLE_TEAM_ID, + APPLE_KEY_ID, + APPLE_SIGNING_KEY_PATH, + } = process.env; + + if ( + !APPLE_CLIENT_ID || + !APPLE_TEAM_ID || + !APPLE_KEY_ID || + !APPLE_SIGNING_KEY_PATH + ) { + return undefined; + } + + const privateKey = fs.readFileSync(APPLE_SIGNING_KEY_PATH, 'utf8'); + + const toBase64Url = (data: object): string => + Buffer.from(JSON.stringify(data)).toString('base64url'); + + const header = toBase64Url({ alg: 'ES256', kid: APPLE_KEY_ID, typ: 'JWT' }); + const now = Math.floor(Date.now() / 1000); + const payload = toBase64Url({ + iss: APPLE_TEAM_ID, + iat: now, + exp: now + 7 * 24 * 60 * 60, // 7 days — regenerated on every app start + aud: 'https://appleid.apple.com', + sub: APPLE_CLIENT_ID, + }); + + const signingInput = `${header}.${payload}`; + const signature = crypto + .sign('SHA256', Buffer.from(signingInput), { + key: privateKey, + dsaEncoding: 'ieee-p1363', + }) + .toString('base64url'); + + return `${signingInput}.${signature}`; +}; + const getGooglePublicKey = async (kid: string) => { const res = await fetch(GOOGLE_CERTS_URL); const { keys } = (await res.json()) as { @@ -617,7 +662,10 @@ const createAuth = (): BetterAuthHandler => { ...(process.env.APPLE_CLIENT_ID && { apple: { clientId: process.env.APPLE_CLIENT_ID, - clientSecret: process.env.APPLE_CLIENT_SECRET ?? '', + clientSecret: + generateAppleClientSecret() ?? + process.env.APPLE_CLIENT_SECRET ?? + '', appBundleIdentifier: process.env.APPLE_APP_BUNDLE_ID || undefined, redirectURI: getSocialRedirectUri('apple'), }, From 2fa68764d8891c39a40716597653bf607095452b Mon Sep 17 00:00:00 2001 From: Chris Bongers Date: Mon, 23 Mar 2026 09:12:19 +0200 Subject: [PATCH 2/2] fix: image removal --- .infra/Pulumi.adhoc.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.infra/Pulumi.adhoc.yaml b/.infra/Pulumi.adhoc.yaml index 4ed92f5504..cb4ac91d5e 100644 --- a/.infra/Pulumi.adhoc.yaml +++ b/.infra/Pulumi.adhoc.yaml @@ -131,6 +131,5 @@ config: secure: v1:AeanAY9fDkWD2gSg:zleimS/qqmb3SCjeHTQ7Mp8QINMs/DcqFE0= signingKey: secure: v1:+2csKe943/E3qzYp:aOG0lDimtWcUCbASDNy9RpxsKnb6eV3JJ31xYtbs9NA3yeoDVBsMvLK0sba0r7lRgGg/SIqCHawAa/GyC4v3dW8rVfgbiAyvLEf0rdaXItVYM2HUimTK+bj+DQ6bLY9nol8JqaRaHfg7NicC+QHnCQogxsiUiS/U9GsXiBEWTQYBW50YSMMKBCzQV2OJU23434f6/EVL1m4scyKHL4wPUDVWi19lOhBAlWTutSXLWt113fafaaS44AIb64mspiThAeuQUAof1tSFn9dc+WgCyekQRQ7RGBcgoANubMthpU540cP3B9PU93TOnh6It1XOC3dYjsMTSWZjNFCXIeJs8+Y8NqbL/g1oOhFn3LrTtJ7c - api:image: api-image:tilt-0414354c2f7cfdc5 secretsprovider: gcpkms://projects/daily-ops/locations/global/keyRings/pulumi/cryptoKeys/adhoc-secrets encryptedkey: CiQAUBCtsAuGPHI7MeeWrccjf/ydEN57pW/AH+8NWsRvdPXM6e8SSABTzSDRfy71YqxOXL5RHxmOL5aU0fwHtiyHj4R5XRZbwtTUivxPk0qEIMtvbQSBCu7NE6CSd+uvaq5B/yz0DU8FWvGuYUzJXQ==