mirror of
https://github.com/hanzoai/passport-apple.git
synced 2026-09-23 23:47:46 +00:00
Initial test
This commit is contained in:
+2
-1
@@ -1 +1,2 @@
|
|||||||
node_modules
|
node_modules
|
||||||
|
.AppleDouble
|
||||||
|
|||||||
+4
-3
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "passport-apple",
|
"name": "passport-apple",
|
||||||
"version": "0.0.1",
|
"version": "0.0.2",
|
||||||
"description": "Passport strategy for Sign in with Apple",
|
"description": "Passport strategy for Sign in with Apple",
|
||||||
"main": "index.js",
|
"main": "src/strategy.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
@@ -31,6 +31,7 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/ananay/passport-apple#readme",
|
"homepage": "https://github.com/ananay/passport-apple#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"passport-oauth2": "^1.5.0"
|
"passport-oauth2": "^1.5.0",
|
||||||
|
"jsonwebtoken": "^8.5.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+124
@@ -0,0 +1,124 @@
|
|||||||
|
/**
|
||||||
|
* Passport Strategy that implements "Sign in with Apple"
|
||||||
|
* @author: Ananay Arora <[email protected]>
|
||||||
|
*/
|
||||||
|
|
||||||
|
const OAuth2Strategy = require('passport-oauth2'),
|
||||||
|
crypto = require('crypto'),
|
||||||
|
AppleClientSecret = require("./token"),
|
||||||
|
util = require('util')
|
||||||
|
querystring = require('querystring'),
|
||||||
|
jwt = require('jsonwebtoken');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Passport Strategy Constructor
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
*
|
||||||
|
* passport.use(new AppleStrategy({
|
||||||
|
* clientID: "",
|
||||||
|
* teamID: "",
|
||||||
|
* callbackURL: "",
|
||||||
|
* keyID: "",
|
||||||
|
* privateKeyLocation: ""
|
||||||
|
* }, function(accessToken, refreshToken, idToken, profile, cb) {
|
||||||
|
* // Here, check if the idToken exists in your database!
|
||||||
|
* cb(null, idToken);
|
||||||
|
* }));
|
||||||
|
*
|
||||||
|
* @param {object} options - Configuration options
|
||||||
|
* @param {string} options.clientID – Client ID (also known as the Services ID
|
||||||
|
* in Apple's Developer Portal). Example: com.ananayarora.app
|
||||||
|
* @param {string} options.teamID – Team ID for the Apple Developer Account
|
||||||
|
* found on top right corner of the developers page
|
||||||
|
* @param {string} options.keyID – The identifier for the private key on the Apple
|
||||||
|
* Developer Account page
|
||||||
|
* @param {string} options.callbackURL – The identifier for the private key on the Apple
|
||||||
|
* Developer Account page
|
||||||
|
* @param {string} options.privateKeyLocation - Location to the private key
|
||||||
|
* @param {function} verify
|
||||||
|
* @access public
|
||||||
|
*/
|
||||||
|
function Strategy(options, verify) {
|
||||||
|
// Set the URLs
|
||||||
|
options = options || {};
|
||||||
|
options.authorizationURL = options.authorizationURL || 'https://appleid.apple.com/auth/authorize';
|
||||||
|
options.tokenURL = options.tokenURL || 'https://appleid.apple.com/auth/token';
|
||||||
|
|
||||||
|
// Make the OAuth call
|
||||||
|
OAuth2Strategy.call(this, options, verify);
|
||||||
|
this.name = 'apple';
|
||||||
|
|
||||||
|
// Initiliaze the client_secret generator
|
||||||
|
const _tokenGenerator = new AppleClientSecret({
|
||||||
|
"client_id": options.clientID,
|
||||||
|
"team_id": options.teamID,
|
||||||
|
"key_id": options.keyID
|
||||||
|
}, options.privateKeyLocation);
|
||||||
|
|
||||||
|
// Get the OAuth Access Token from Apple's server
|
||||||
|
// using the grant code / refresh token.
|
||||||
|
|
||||||
|
this._oauth2.getOAuthAccessToken = function(code, params, callback) {
|
||||||
|
// Generate the client_secret using the library
|
||||||
|
_tokenGenerator.generate().then((client_secret) => {
|
||||||
|
params = params || {};
|
||||||
|
const codeParam = params.grant_type === 'refresh_token' ? 'refresh_token' : 'code';
|
||||||
|
params[codeParam] = code;
|
||||||
|
params['client_id'] = this._clientId;
|
||||||
|
params['client_secret'] = client_secret;
|
||||||
|
|
||||||
|
const post_data = querystring.stringify(params);
|
||||||
|
const post_headers = {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
|
};
|
||||||
|
this._request(
|
||||||
|
'POST',
|
||||||
|
this._getAccessTokenUrl(),
|
||||||
|
post_headers,
|
||||||
|
post_data,
|
||||||
|
null,
|
||||||
|
function(error, data, response) {
|
||||||
|
if (error) {
|
||||||
|
callback(error);
|
||||||
|
} else {
|
||||||
|
let results = JSON.parse(data);
|
||||||
|
let access_token = results.access_token;
|
||||||
|
let refresh_token = results.refresh_token;
|
||||||
|
let id_token = jwt.decode(results.id_token).sub;
|
||||||
|
callback(null, access_token, refresh_token, id_token, results);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}).catch((error) => {
|
||||||
|
// Log to the console if the token generation fails.
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inherit from `OAuth2Strategy`.
|
||||||
|
util.inherits(Strategy, OAuth2Strategy);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process the authentication request
|
||||||
|
* @param {http.IncomingMessage} req
|
||||||
|
* @param {object} options
|
||||||
|
* @access protected
|
||||||
|
*/
|
||||||
|
Strategy.prototype.authenticate = function(req, options) {
|
||||||
|
OAuth2Strategy.prototype.authenticate.call(this, req, options);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modify the authorization params. Currently adds
|
||||||
|
* the missing `state` parameter
|
||||||
|
* @param {object} options
|
||||||
|
* @access protected
|
||||||
|
*/
|
||||||
|
Strategy.prototype.authorizationParams = function (options) {
|
||||||
|
options.state = crypto.randomBytes(5).toString('hex');
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Strategy;
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
/**
|
||||||
|
* Generates a client secret for Apple auth
|
||||||
|
* using the private key.
|
||||||
|
* @author: Ananay Arora <[email protected]>
|
||||||
|
*/
|
||||||
|
|
||||||
|
const jwt = require('jsonwebtoken');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
class AppleClientSecret {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {object} config
|
||||||
|
* @param {string} config.client_id
|
||||||
|
* @param {string} config.team_id
|
||||||
|
* @param {string} config.redirect_uri
|
||||||
|
* @param {string} config.key_id
|
||||||
|
* @param {*} privateKeyLocation
|
||||||
|
*/
|
||||||
|
constructor(config, privateKeyLocation) {
|
||||||
|
this._config = config;
|
||||||
|
this._privateKeyLocation = privateKeyLocation;
|
||||||
|
this.generate = this.generate.bind(this);
|
||||||
|
this._generateToken = this._generateToken.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the JWT token
|
||||||
|
* @param {string} clientId
|
||||||
|
* @param {string} teamId
|
||||||
|
* @param {string} privateKey
|
||||||
|
* @param {int} expiration
|
||||||
|
* @returns {Promise<string>} token
|
||||||
|
*/
|
||||||
|
_generateToken(clientId, teamId, privateKey, exp, keyid) {
|
||||||
|
return new Promise (
|
||||||
|
(resolve, reject) => {
|
||||||
|
// Curate the claims
|
||||||
|
const claims = {
|
||||||
|
iss: teamId,
|
||||||
|
iat: Math.floor(Date.now() / 1000),
|
||||||
|
exp,
|
||||||
|
aud: 'https://appleid.apple.com',
|
||||||
|
sub: clientId,
|
||||||
|
};
|
||||||
|
// Sign the claims using the private key
|
||||||
|
jwt.sign(claims, privateKey, {
|
||||||
|
algorithm: 'ES256',
|
||||||
|
keyid
|
||||||
|
}, (err, token) => {
|
||||||
|
if (err) {
|
||||||
|
reject("AppleAuth Error – Error occurred while signing: " + err);
|
||||||
|
}
|
||||||
|
resolve(token);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the private key file calls
|
||||||
|
* the token generation method
|
||||||
|
* @returns {Promise<string>} token - The generated client secret
|
||||||
|
*/
|
||||||
|
generate() {
|
||||||
|
return new Promise (
|
||||||
|
(resolve, reject) => {
|
||||||
|
fs.readFile(this._privateKeyLocation, (err, privateKey) => {
|
||||||
|
if (err) {
|
||||||
|
reject("AppleAuth Error - Couldn't read your Private Key file: " + err);
|
||||||
|
}
|
||||||
|
let exp = Math.floor(Date.now() / 1000) + ( 86400 * 180 ); // Make it expire within 6 months
|
||||||
|
this._generateToken(
|
||||||
|
this._config.client_id,
|
||||||
|
this._config.team_id,
|
||||||
|
privateKey,
|
||||||
|
exp,
|
||||||
|
this._config.key_id
|
||||||
|
).then((token) => {
|
||||||
|
resolve(token);
|
||||||
|
}).catch((err) => {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
module.exports = AppleClientSecret;
|
||||||
Reference in New Issue
Block a user