Harte Paywall

This commit is contained in:
2026-04-29 21:16:16 +02:00
committed by Timo Knuth
parent 0f933da3c9
commit d37b49f1f6
10 changed files with 305 additions and 129 deletions

View File

@@ -12,6 +12,13 @@ const APPLE_AUDIENCE = (
|| process.env.IOS_BUNDLE_ID
|| 'com.greenlens.app'
).trim();
const APPLE_ALLOWED_AUDIENCES = [
APPLE_AUDIENCE,
...(process.env.APPLE_ALLOWED_AUDIENCES || '')
.split(',')
.map((value) => value.trim())
.filter(Boolean),
];
let appleJwksCache = { keys: [], expiresAt: 0 };
// ─── Minimal JWT (HS256, no external deps) ─────────────────────────────────
@@ -134,7 +141,7 @@ const verifyAppleIdentityToken = async (identityToken) => {
const publicKey = crypto.createPublicKey({ key: jwk, format: 'jwk' });
const validSignature = verifier.verify(publicKey, Buffer.from(encodedSignature, 'base64url'));
const nowSeconds = Math.floor(Date.now() / 1000);
const expectedAudiences = new Set([APPLE_AUDIENCE, 'com.greenlens.app'].filter(Boolean));
const expectedAudiences = new Set(APPLE_ALLOWED_AUDIENCES.filter(Boolean));
if (
!validSignature
@@ -143,6 +150,14 @@ const verifyAppleIdentityToken = async (identityToken) => {
|| !claims.sub
|| (claims.exp && nowSeconds > Number(claims.exp))
) {
console.warn('Apple identityToken verification failed.', {
validSignature,
issuer: claims.iss,
audience: claims.aud,
expectedAudiences: Array.from(expectedAudiences),
hasSubject: Boolean(claims.sub),
expired: Boolean(claims.exp && nowSeconds > Number(claims.exp)),
});
const error = new Error('Apple identityToken could not be verified.');
error.code = 'APPLE_AUTH_INVALID';
error.status = 401;
@@ -241,7 +256,7 @@ const signInWithApple = async (db, identityToken, profile = {}) => {
'SELECT id, email, name FROM auth_users WHERE apple_subject = $1',
[appleSubject],
);
if (existingByApple) return existingByApple;
if (existingByApple) return { ...existingByApple, isNewUser: false };
if (!normalizedEmail) {
const err = new Error('Apple did not return an email for this account.');
@@ -262,7 +277,7 @@ const signInWithApple = async (db, identityToken, profile = {}) => {
'UPDATE auth_users SET apple_subject = $1, auth_provider = $2, name = $3 WHERE id = $4',
[appleSubject, 'apple', nextName, existingByEmail.id],
);
return { ...existingByEmail, name: nextName };
return { ...existingByEmail, name: nextName, isNewUser: false };
}
const id = `usr_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
@@ -273,7 +288,7 @@ const signInWithApple = async (db, identityToken, profile = {}) => {
VALUES ($1, $2, $3, NULL, $4, $5)`,
[id, normalizedEmail, name, 'apple', appleSubject],
);
return { id, email: normalizedEmail, name };
return { id, email: normalizedEmail, name, isNewUser: true };
};
module.exports = { ensureAuthSchema, signUp, login, signInWithApple, issueToken, verifyJwt, verifyAppleIdentityToken };