Merge branch 'develop' into release
This commit is contained in:
@@ -146,6 +146,41 @@ api.loginSocial = {
|
||||
},
|
||||
};
|
||||
|
||||
// Called by apple for web authentication.
|
||||
api.redirectApple = {
|
||||
method: 'POST',
|
||||
middlewares: [authWithHeaders({
|
||||
optional: true,
|
||||
})],
|
||||
url: '/user/auth/apple',
|
||||
async handler (req, res) {
|
||||
if (req.body.id_token) {
|
||||
req.body.network = 'apple';
|
||||
return loginSocial(req, res);
|
||||
}
|
||||
let url = `/static/apple-redirect?code=${req.body.code}`;
|
||||
if (req.body.user) {
|
||||
const { name } = JSON.parse(req.body.user);
|
||||
url += `&name=${name.firstName} ${name.lastName}`;
|
||||
}
|
||||
return res.redirect(303, url);
|
||||
},
|
||||
};
|
||||
|
||||
// Called as a callback by Apple. Internal route
|
||||
// Can be passed `code` and `name` as query parameters
|
||||
api.loginApple = {
|
||||
method: 'GET',
|
||||
middlewares: [authWithHeaders({
|
||||
optional: true,
|
||||
})],
|
||||
url: '/user/auth/apple',
|
||||
async handler (req, res) {
|
||||
req.body.network = 'apple';
|
||||
return loginSocial(req, res);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @api {put} /api/v3/user/auth/update-username Update username
|
||||
* @apiDescription Update and verify the user's username
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
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' });
|
||||
|
||||
return {
|
||||
id: verifiedPayload.sub,
|
||||
emails: [{ value: verifiedPayload.email }],
|
||||
name: verifiedPayload.name || req.body.name || req.query.name,
|
||||
};
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
generateUsername,
|
||||
loginRes,
|
||||
} from './utils';
|
||||
import { appleProfile } from './apple';
|
||||
import { model as User } from '../../models/user';
|
||||
import { model as EmailUnsubscription } from '../../models/emailUnsubscription';
|
||||
import { sendTxn as sendTxnEmail } from '../email';
|
||||
@@ -24,14 +25,21 @@ function _passportProfile (network, accessToken) {
|
||||
|
||||
export async function loginSocial (req, res) { // eslint-disable-line import/prefer-default-export
|
||||
const existingUser = res.locals.user;
|
||||
const accessToken = req.body.authResponse.access_token;
|
||||
const { network } = req.body;
|
||||
|
||||
const isSupportedNetwork = common.constants.SUPPORTED_SOCIAL_NETWORKS
|
||||
.find(supportedNetwork => supportedNetwork.key === network);
|
||||
if (!isSupportedNetwork) throw new BadRequest(res.t('unsupportedNetwork'));
|
||||
|
||||
const profile = await _passportProfile(network, accessToken);
|
||||
let profile = {};
|
||||
if (network === 'apple') {
|
||||
profile = await appleProfile(req);
|
||||
} else {
|
||||
const accessToken = req.body.authResponse.access_token;
|
||||
profile = await _passportProfile(network, accessToken);
|
||||
}
|
||||
|
||||
if (!profile.id) throw new BadRequest(res.t('invalidData'));
|
||||
|
||||
let user = await User.findOne({
|
||||
[`auth.${network}.id`]: profile.id,
|
||||
@@ -80,7 +88,7 @@ export async function loginSocial (req, res) { // eslint-disable-line import/pre
|
||||
user.newUser = true;
|
||||
}
|
||||
|
||||
loginRes(user, req, res);
|
||||
const response = loginRes(user, req, res);
|
||||
|
||||
// Clean previous email preferences
|
||||
if (
|
||||
@@ -114,5 +122,5 @@ export async function loginSocial (req, res) { // eslint-disable-line import/pre
|
||||
});
|
||||
}
|
||||
|
||||
return null;
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import nconf from 'nconf';
|
||||
import shortid from 'short-uuid';
|
||||
import url from 'url';
|
||||
|
||||
import { NotAuthorized } from '../errors';
|
||||
|
||||
@@ -18,6 +19,11 @@ export function loginRes (user, req, res) {
|
||||
{ communityManagerEmail: COMMUNITY_MANAGER_EMAIL, userId: user._id },
|
||||
));
|
||||
}
|
||||
const urlPath = url.parse(req.url).pathname;
|
||||
if (req.headers['x-client'] === 'habitica-android' && urlPath.includes('apple')) {
|
||||
// This is a workaround for android not being able to handle sign in with apple better.
|
||||
return res.redirect(`/?id=${user._id}&key=${user.apiToken}&newUser=${user.newUser || false}`);
|
||||
}
|
||||
|
||||
const responseData = {
|
||||
id: user._id,
|
||||
@@ -25,6 +31,5 @@ export function loginRes (user, req, res) {
|
||||
newUser: user.newUser || false,
|
||||
username: user.auth.local.username,
|
||||
};
|
||||
|
||||
return res.respond(200, responseData);
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ export default new Schema({
|
||||
$type: Schema.Types.Mixed,
|
||||
default: () => ({}),
|
||||
},
|
||||
apple: { $type: Schema.Types.Mixed, default: () => ({}) },
|
||||
local: {
|
||||
email: {
|
||||
$type: String,
|
||||
|
||||
Reference in New Issue
Block a user