Files
habitica/website/server/libs/auth/apple.js
T
Kalista Payne 3d3db1bdd9 Require email in social reg edge case (#15634)
* apply email passed via body if it is missing from apple profile

* Add Web UI to set email if apple does not provide one

* fix lint

* remove trailing space

* fix(register): show field if social auth without email

* fix(ux): add explanatory text

* fix(lint): max-len

* fix(data): remove unused field

* fix(auth): pass email around as necessary in Apple flow

* fix(auth): still wrong place argh

* Fix(auth): handle email in Apple registration flow

---------

Co-authored-by: Phillip Thelen <phillip@habitica.com>
Co-authored-by: Hafiz <hafizbhamidi@gmail.com>
2026-04-02 15:16:43 -05:00

58 lines
1.9 KiB
JavaScript

import AppleAuth from 'apple-auth';
import nconf from 'nconf';
import jwt from 'jsonwebtoken';
import jwksClient from 'jwks-rsa';
import util from 'util';
const APPLE_PRIVATE_KEY = nconf.get('APPLE_AUTH_PRIVATE_KEY');
const APPLE_AUTH_CLIENT_ID = nconf.get('APPLE_AUTH_CLIENT_ID');
const APPLE_TEAM_ID = nconf.get('APPLE_TEAM_ID');
const APPLE_AUTH_KEY_ID = nconf.get('APPLE_AUTH_KEY_ID');
const BASE_URL = nconf.get('BASE_URL');
const appleAuth = new AppleAuth(JSON.stringify({
client_id: APPLE_AUTH_CLIENT_ID, // eslint-disable-line camelcase
team_id: APPLE_TEAM_ID, // eslint-disable-line camelcase
key_id: APPLE_AUTH_KEY_ID, // eslint-disable-line camelcase
redirect_uri: `${BASE_URL}/api/v4/user/auth/apple`, // eslint-disable-line camelcase
scope: 'name email',
}), APPLE_PRIVATE_KEY, 'text');
const APPLE_PUBLIC_KEYS_URL = 'https://appleid.apple.com/auth/keys';
const appleJwksClient = jwksClient({
jwksUri: APPLE_PUBLIC_KEYS_URL,
});
const getAppleSigningKey = util.promisify(appleJwksClient.getSigningKey);
export async function appleProfile (req) {
const code = req.body.code ? req.body.code : req.query.code;
const passedToken = req.body.id_token ? req.body.id_token : req.query.id_token;
let idToken;
if (code) {
const response = await appleAuth.accessToken(code);
idToken = response.id_token;
} else if (passedToken) {
idToken = passedToken;
}
const decodedToken = jwt.decode(idToken, { complete: true });
const signingKey = await getAppleSigningKey(decodedToken.header.kid);
const applePublicKey = signingKey.getPublicKey();
const verifiedPayload = await jwt.verify(idToken, applePublicKey, { algorithms: 'RS256' });
let { email } = verifiedPayload;
if ((!email || email === '') && req.body.email) {
email = req.body.email;
}
return {
id: verifiedPayload.sub,
emails: [{ value: email }],
name: verifiedPayload.name || req.body.name || req.query.name,
idToken,
};
}