forked from parse-community/parse-server
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfacebook.js
143 lines (123 loc) · 4.31 KB
/
facebook.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
// Helper functions for accessing the Facebook Graph API.
const Parse = require('parse/node').Parse;
const crypto = require('crypto');
const jwksClient = require('jwks-rsa');
const util = require('util');
const jwt = require('jsonwebtoken');
const httpsRequest = require('./httpsRequest');
const authUtils = require('./utils');
const TOKEN_ISSUER = 'https://www.facebook.com';
function getAppSecretPath(authData, options = {}) {
const appSecret = options.appSecret;
if (!appSecret) {
return '';
}
const appsecret_proof = crypto
.createHmac('sha256', appSecret)
.update(authData.access_token)
.digest('hex');
return `&appsecret_proof=${appsecret_proof}`;
}
function validateGraphToken(authData, options) {
return graphRequest(
'me?fields=id&access_token=' + authData.access_token + getAppSecretPath(authData, options)
).then(data => {
if ((data && data.id == authData.id) || (process.env.TESTING && authData.id === 'test')) {
return;
}
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Facebook auth is invalid for this user.');
});
}
async function validateGraphAppId(appIds, authData, options) {
var access_token = authData.access_token;
if (process.env.TESTING && access_token === 'test') {
return;
}
if (!Array.isArray(appIds)) {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'appIds must be an array.');
}
if (!appIds.length) {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Facebook auth is not configured.');
}
const data = await graphRequest(
`app?access_token=${access_token}${getAppSecretPath(authData, options)}`
);
if (!data || !appIds.includes(data.id)) {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Facebook auth is invalid for this user.');
}
}
const getFacebookKeyByKeyId = async (keyId, cacheMaxEntries, cacheMaxAge) => {
const client = jwksClient({
jwksUri: `${TOKEN_ISSUER}/.well-known/oauth/openid/jwks/`,
cache: true,
cacheMaxEntries,
cacheMaxAge,
});
const asyncGetSigningKeyFunction = util.promisify(client.getSigningKey);
let key;
try {
key = await asyncGetSigningKeyFunction(keyId);
} catch (error) {
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
`Unable to find matching key for Key ID: ${keyId}`
);
}
return key;
};
const verifyIdToken = async ({ token, id }, { clientId, cacheMaxEntries, cacheMaxAge }) => {
if (!token) {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'id token is invalid for this user.');
}
const { kid: keyId, alg: algorithm } = authUtils.getHeaderFromToken(token);
const ONE_HOUR_IN_MS = 3600000;
let jwtClaims;
cacheMaxAge = cacheMaxAge || ONE_HOUR_IN_MS;
cacheMaxEntries = cacheMaxEntries || 5;
const facebookKey = await getFacebookKeyByKeyId(keyId, cacheMaxEntries, cacheMaxAge);
const signingKey = facebookKey.publicKey || facebookKey.rsaPublicKey;
try {
jwtClaims = jwt.verify(token, signingKey, {
algorithms: algorithm,
// the audience can be checked against a string, a regular expression or a list of strings and/or regular expressions.
audience: clientId,
});
} catch (exception) {
const message = exception.message;
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, `${message}`);
}
if (jwtClaims.iss !== TOKEN_ISSUER) {
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
`id token not issued by correct OpenID provider - expected: ${TOKEN_ISSUER} | from: ${jwtClaims.iss}`
);
}
if (jwtClaims.sub !== id) {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'auth data is invalid for this user.');
}
return jwtClaims;
};
// Returns a promise that fulfills iff this user id is valid.
function validateAuthData(authData, options) {
if (authData.token) {
return verifyIdToken(authData, options);
} else {
return validateGraphToken(authData, options);
}
}
// Returns a promise that fulfills iff this app id is valid.
function validateAppId(appIds, authData, options) {
if (authData.token) {
return Promise.resolve();
} else {
return validateGraphAppId(appIds, authData, options);
}
}
// A promisey wrapper for FB graph requests.
function graphRequest(path) {
return httpsRequest.get('https://graph.facebook.com/' + path);
}
module.exports = {
validateAppId: validateAppId,
validateAuthData: validateAuthData,
};