Harte Paywall
This commit is contained in:
@@ -1066,7 +1066,13 @@ app.post('/auth/apple', async (request, response) => {
|
||||
}
|
||||
const user = await authSignInWithApple(db, identityToken, { appleUser, email, name });
|
||||
const token = issueToken(user.id, user.email, user.name);
|
||||
response.status(200).json({ userId: user.id, email: user.email, name: user.name, token });
|
||||
response.status(200).json({
|
||||
userId: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
token,
|
||||
isNewUser: Boolean(user.isNewUser),
|
||||
});
|
||||
} catch (error) {
|
||||
const status = error.status || 500;
|
||||
response.status(status).json({ code: error.code || 'SERVER_ERROR', message: error.message });
|
||||
|
||||
@@ -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 };
|
||||
|
||||
Reference in New Issue
Block a user