parent
261c9e1ce1
commit
1441feb667
@ -0,0 +1,76 @@ |
||||
import { KJUR } from 'jsrsasign'; |
||||
import { ServiceConfiguration } from 'meteor/service-configuration'; |
||||
|
||||
import { CustomOAuth } from '../../custom-oauth/server/custom_oauth_server'; |
||||
import { settings, settingsRegistry } from '../../settings/server'; |
||||
|
||||
const config = { |
||||
serverURL: 'https://appleid.apple.com', |
||||
tokenPath: '/auth/token', |
||||
scope: 'name email', |
||||
mergeUsers: true, |
||||
accessTokenParam: 'access_token', |
||||
loginStyle: 'popup', |
||||
}; |
||||
|
||||
new CustomOAuth('apple', config); |
||||
|
||||
settingsRegistry.addGroup('OAuth', function () { |
||||
this.section('Apple', function () { |
||||
this.add('Accounts_OAuth_Apple', false, { type: 'boolean', public: true }); |
||||
|
||||
this.add('Accounts_OAuth_Apple_id', '', { type: 'string', public: true }); |
||||
this.add('Accounts_OAuth_Apple_secretKey', '', { type: 'string', multiline: true }); |
||||
|
||||
this.add('Accounts_OAuth_Apple_iss', '', { type: 'string' }); |
||||
this.add('Accounts_OAuth_Apple_kid', '', { type: 'string' }); |
||||
}); |
||||
}); |
||||
|
||||
settings.watchMultiple( |
||||
[ |
||||
'Accounts_OAuth_Apple', |
||||
'Accounts_OAuth_Apple_id', |
||||
'Accounts_OAuth_Apple_secretKey', |
||||
'Accounts_OAuth_Apple_iss', |
||||
'Accounts_OAuth_Apple_kid', |
||||
], |
||||
([enabled, clientId, serverSecret, iss, kid]) => { |
||||
if (!enabled) { |
||||
return ServiceConfiguration.configurations.remove({ |
||||
service: 'apple', |
||||
}); |
||||
} |
||||
|
||||
const HEADER = { |
||||
kid, |
||||
alg: 'ES256', |
||||
}; |
||||
|
||||
const tokenPayload = { |
||||
iss, |
||||
iat: Math.floor(Date.now() / 1000), |
||||
exp: Math.floor(Date.now() / 1000) + 300, |
||||
aud: 'https://appleid.apple.com', |
||||
sub: clientId, |
||||
}; |
||||
|
||||
const secret = KJUR.jws.JWS.sign(null, HEADER, tokenPayload, serverSecret as string); |
||||
|
||||
ServiceConfiguration.configurations.upsert( |
||||
{ |
||||
service: 'apple', |
||||
}, |
||||
{ |
||||
$set: { |
||||
// We'll hide this button on Web Client
|
||||
showButton: false, |
||||
secret, |
||||
enabled: settings.get('Accounts_OAuth_Apple'), |
||||
loginStyle: 'popup', |
||||
clientId, |
||||
}, |
||||
}, |
||||
); |
||||
}, |
||||
); |
||||
@ -1,30 +1 @@ |
||||
import { ServiceConfiguration } from 'meteor/service-configuration'; |
||||
|
||||
import { settings, settingsRegistry } from '../../settings/server'; |
||||
|
||||
settingsRegistry.addGroup('OAuth', function () { |
||||
this.section('Apple', function () { |
||||
this.add('Accounts_OAuth_Apple', false, { type: 'boolean', public: true }); |
||||
}); |
||||
}); |
||||
|
||||
settings.watch('Accounts_OAuth_Apple', (enabled) => { |
||||
if (!enabled) { |
||||
return ServiceConfiguration.configurations.remove({ |
||||
service: 'apple', |
||||
}); |
||||
} |
||||
|
||||
ServiceConfiguration.configurations.upsert( |
||||
{ |
||||
service: 'apple', |
||||
}, |
||||
{ |
||||
$set: { |
||||
// We'll hide this button on Web Client
|
||||
showButton: false, |
||||
enabled: settings.get('Accounts_OAuth_Apple'), |
||||
}, |
||||
}, |
||||
); |
||||
}); |
||||
import './appleOauthRegisterService'; |
||||
|
||||
@ -1,9 +1,18 @@ |
||||
<template name='loginServices'> |
||||
<template name="loginServices"> |
||||
{{#if loginService.length}} |
||||
<div class="oauth-login buttons-group"> |
||||
{{#each loginService}} |
||||
<button type="button" class="rc-button rc-button--secondary external-login {{service.service}}" title="{{displayName}}" style="{{#if service.buttonColor}}background-color:{{service.buttonColor}};{{/if}}{{#if service.buttonLabelColor}}color:{{service.buttonLabelColor}};{{/if}}"><i class="icon-{{icon}} service-icon"></i><i class="icon-spin animate-spin loading-icon hidden"></i><span>{{service.buttonLabelText}}</span></button> |
||||
{{/each}} |
||||
</div> |
||||
{{/if}} |
||||
<div class="oauth-login buttons-group"> |
||||
{{#each loginService}} |
||||
<button |
||||
type="button" |
||||
class="rc-button rc-button--secondary external-login {{service.service}}" |
||||
title="{{displayName}}" |
||||
style="{{#if service.buttonColor}}background-color:{{service.buttonColor}};{{/if}}{{#if service.buttonLabelColor}}color:{{service.buttonLabelColor}};{{/if}}" |
||||
> |
||||
<i class="icon-{{icon}} service-icon"></i |
||||
><i class="icon-spin animate-spin loading-icon hidden"></i |
||||
><span>{{service.buttonLabelText}}</span> |
||||
</button> |
||||
{{/each}} |
||||
</div> |
||||
{{/if}} {{> AppleOauthButton}} |
||||
</template> |
||||
|
||||
@ -1,6 +1,6 @@ |
||||
export const createAnchor = (id: string): HTMLDivElement => { |
||||
export const createAnchor = (id: string, target = document.body): HTMLDivElement => { |
||||
const div = document.createElement('div'); |
||||
div.id = id; |
||||
document.body.appendChild(div); |
||||
target.appendChild(div); |
||||
return div; |
||||
}; |
||||
|
||||
@ -0,0 +1,93 @@ |
||||
import { Accounts } from 'meteor/accounts-base'; |
||||
import React, { FC, useCallback, useEffect, useLayoutEffect, useRef } from 'react'; |
||||
|
||||
import { useAbsoluteUrl } from '../../../contexts/ServerContext'; |
||||
import { useSetting } from '../../../contexts/SettingsContext'; |
||||
|
||||
export const AppleOauthButton: FC = () => { |
||||
const enabled = useSetting('Accounts_OAuth_Apple'); |
||||
const absoluteUrl = useAbsoluteUrl(); |
||||
const appleClientID = useSetting('Accounts_OAuth_Apple_id'); |
||||
|
||||
const redirectURI = absoluteUrl('_oauth/apple'); |
||||
|
||||
useEffect(() => { |
||||
const success = (data: any): void => { |
||||
const { authorization, user } = data.detail; |
||||
|
||||
Accounts.callLoginMethod({ |
||||
methodArguments: [ |
||||
{ |
||||
serviceName: 'apple', |
||||
identityToken: authorization.id_token, |
||||
...(user && { |
||||
fullName: { |
||||
givenName: user.name.firstName, |
||||
familyName: user.name.lastName, |
||||
}, |
||||
email: user.email, |
||||
}), |
||||
}, |
||||
], |
||||
userCallback: console.log, |
||||
}); |
||||
}; |
||||
|
||||
const error = (error: any): void => { |
||||
// handle error.
|
||||
console.error(error); |
||||
}; |
||||
document.addEventListener('AppleIDSignInOnSuccess', success); |
||||
// Listen for authorization failures
|
||||
document.addEventListener('AppleIDSignInOnFailure', error); |
||||
|
||||
return (): void => { |
||||
document.removeEventListener('AppleIDSignInOnSuccess', success); |
||||
document.removeEventListener('AppleIDSignInOnFailure', error); |
||||
}; |
||||
}, []); |
||||
|
||||
const scriptLoadedHandler = useCallback(() => { |
||||
if (!enabled) { |
||||
return; |
||||
} |
||||
(window as any).AppleID.auth.init({ |
||||
clientId: appleClientID, |
||||
scope: 'name email', |
||||
redirectURI, |
||||
usePopup: true, |
||||
}); |
||||
}, [enabled, appleClientID, redirectURI]); |
||||
|
||||
const ref = useRef<HTMLScriptElement>(); |
||||
|
||||
useEffect(() => { |
||||
if ((window as any).AppleID) { |
||||
scriptLoadedHandler(); |
||||
return; |
||||
} |
||||
if (!ref.current) { |
||||
return; |
||||
} |
||||
ref.current.onload = scriptLoadedHandler; |
||||
}, [scriptLoadedHandler]); |
||||
|
||||
useLayoutEffect(() => { |
||||
const script = document.createElement('script'); |
||||
script.src = 'https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js'; |
||||
script.async = true; |
||||
ref.current = script; |
||||
document.body.appendChild(script); |
||||
return (): void => { |
||||
document.body.removeChild(script); |
||||
}; |
||||
}, []); |
||||
|
||||
if (!enabled) { |
||||
return null; |
||||
} |
||||
|
||||
return <div id='appleid-signin' data-height='40px'></div>; |
||||
}; |
||||
|
||||
export default AppleOauthButton; |
||||
Loading…
Reference in new issue