import { MeteorError } from '@rocket.chat/core-services'; import { Users } from '@rocket.chat/models'; import express from 'express'; import { Accounts } from 'meteor/accounts-base'; import { ServiceConfiguration } from 'meteor/service-configuration'; import passport from 'passport'; import { Strategy as AppleStrategy } from 'passport-apple'; import type { Profile } from 'passport-apple'; import { AppleCustomOAuth } from './AppleCustomOAuth'; import { handleIdentityToken } from './handleIdentityToken'; import { config } from '../../../../app/apple/lib/config'; import { oAuthRouter } from '../../../configuration/configurePassport'; import { settings } from '../../../settings'; import { allowPassportOAuthMiddleware } from '../../oauth/allowPassportOAuthMiddleware'; import { passportOAuthCallback } from '../../oauth/passportOAuthCallback'; import { removeOAuthRoutes } from '../../oauth/removeOAuthRoutes'; new AppleCustomOAuth('apple', config); type RequestWithAppleProfile = { appleProfile?: { name?: { firstName?: string; middleName?: string; lastName?: string; }; }; }; function isRequestWithAppleProfile(req: object): req is RequestWithAppleProfile { if ( 'appleProfile' in req && req.appleProfile && typeof req.appleProfile === 'object' && 'name' in req.appleProfile && req.appleProfile.name && typeof req.appleProfile.name === 'object' ) { return true; } return false; } settings.watchMultiple( [ 'Accounts_OAuth_Apple', 'Accounts_OAuth_Apple_id', 'Accounts_OAuth_Apple_secretKey', 'Accounts_OAuth_Apple_iss', 'Accounts_OAuth_Apple_kid', 'Accounts_OAuth_Use_Modern_Flow', ], async ([enabled, clientId, serverSecret, iss, kid, useModernFlow]) => { passport.unuse('apple'); removeOAuthRoutes('apple'); if (!useModernFlow) { return; } if (!enabled) { return ServiceConfiguration.configurations.removeAsync({ service: 'apple', }); } // if everything is empty but Apple login is enabled, don't show the login button if (!clientId && !serverSecret && !iss && !kid) { await ServiceConfiguration.configurations.upsertAsync( { service: 'apple', }, { $set: { showButton: false, enabled: settings.get('Accounts_OAuth_Apple'), }, }, ); return; } if (typeof clientId !== 'string' || !clientId) { return ServiceConfiguration.configurations.removeAsync({ service: 'apple', }); } passport.use( 'apple', new AppleStrategy( { clientID: clientId, teamID: settings.get('Accounts_OAuth_Apple_iss'), keyID: settings.get('Accounts_OAuth_Apple_kid'), privateKeyString: settings.get('Accounts_OAuth_Apple_secretKey').replace(/\\n/g, '\n'), callbackURL: `${settings.get('Site_Url').replace(/\/$/, '')}/_oauth/apple`, scope: ['name', 'email'], passReqToCallback: true, state: false, }, async (req, accessToken: string, refreshToken: string, idToken: string, _profile: Profile, done) => { try { const serviceData = await handleIdentityToken(idToken, clientId); if (isRequestWithAppleProfile(req)) { const appleName = req.appleProfile?.name; serviceData.name = [appleName?.firstName, appleName?.middleName, appleName?.lastName].filter(Boolean).join(' '); } const user = await Accounts.updateOrCreateUserFromExternalService( 'apple', { accessToken, refreshToken, ...serviceData, }, {}, ); if (!user?.userId || typeof user?.userId !== 'string') { return done(new Error('User not found')); } const userFromDB = await Users.findOneById(user.userId); if (!userFromDB) { return done(new Error('User not found')); } return done(null, userFromDB); } catch (error: any) { done(error); return { type: 'apple', error: new MeteorError(Accounts.LoginCancelledError.numericError, error.message), }; } }, ), ); const callbackHandler = [ allowPassportOAuthMiddleware('apple'), express.urlencoded({ extended: true }), passport.authenticate('apple', { failWithError: true, session: true, keepSessionInfo: true }), passportOAuthCallback(settings.get('Site_Url').replace(/\/$/, '')), ]; oAuthRouter.get( '/oauth/apple', allowPassportOAuthMiddleware('apple'), (req, _res, next) => { const { loginClient } = req.query; if (loginClient === 'mobile' || loginClient === 'desktop') { req.session.loginClient = loginClient; req.session.save(() => { next(); }); } else { //delete stale value from previous sessions if any delete req.session.loginClient; next(); } }, passport.authenticate('apple', { scope: ['name', 'email'], }), ); oAuthRouter .route('/_oauth/apple') .post(...callbackHandler) .get(...callbackHandler); }, );