-
Notifications
You must be signed in to change notification settings - Fork 3.8k
/
Copy pathindex.js
163 lines (149 loc) · 5.85 KB
/
index.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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
/**
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
const functions = require('firebase-functions');
const cookieParser = require('cookie-parser');
const crypto = require('crypto');
// Firebase Setup
const admin = require('firebase-admin');
// @ts-ignore
const serviceAccount = require('./service-account.json');
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: `https://${process.env.GCLOUD_PROJECT}.firebaseio.com`,
});
const OAUTH_SCOPES = ['r_basicprofile', 'r_emailaddress'];
/**
* Creates a configured LinkedIn API Client instance.
*/
function linkedInClient() {
// LinkedIn OAuth 2 setup
// TODO: Configure the `linkedin.client_id` and `linkedin.client_secret` Google Cloud environment variables.
return require('node-linkedin')(
functions.config().linkedin.client_id,
functions.config().linkedin.client_secret,
`https://${process.env.GCLOUD_PROJECT}.firebaseapp.com/popup.html`);
}
/**
* Redirects the User to the LinkedIn authentication consent screen. ALso the '__session' cookie is set for later state
* verification.
*/
exports.redirect = functions.https.onRequest((req, res) => {
const Linkedin = linkedInClient();
cookieParser()(req, res, () => {
const state = req.cookies.__session || crypto.randomBytes(20).toString('hex');
functions.logger.log('Setting verification state:', state);
res.cookie('__session', state.toString(), {
maxAge: 3600000,
secure: true,
httpOnly: true,
});
Linkedin.auth.authorize(res, OAUTH_SCOPES, state.toString());
});
});
/**
* Exchanges a given LinkedIn auth code passed in the 'code' URL query parameter for a Firebase auth token.
* The request also needs to specify a 'state' query parameter which will be checked against the '__session' cookie.
* The Firebase custom auth token is sent back in a JSONP callback function with function name defined by the
* 'callback' query parameter.
*/
exports.token = functions.https.onRequest((req, res) => {
const Linkedin = linkedInClient();
try {
return cookieParser()(req, res, () => {
if (!req.cookies.__session) {
throw new Error('__session cookie not set or expired. Maybe you took too long to authorize. Please try again.');
}
functions.logger.log('Received verification state:', req.cookies.__session);
Linkedin.auth.authorize(OAUTH_SCOPES, req.cookies.__session); // Makes sure the state parameter is set
functions.logger.log('Received auth code:', req.query.code);
functions.logger.log('Received state:', req.query.state);
Linkedin.auth.getAccessToken(res, req.query.code, req.query.state, (error, results) => {
if (error) {
throw error;
}
functions.logger.log('Received Access Token:', results.access_token);
const linkedin = Linkedin.init(results.access_token);
linkedin.people.me(async (error, userResults) => {
if (error) {
throw error;
}
functions.logger.log(
'Auth code exchange result received:',
userResults
);
// We have a LinkedIn access token and the user identity now.
const accessToken = results.access_token;
const linkedInUserID = userResults.id;
const profilePic = userResults.pictureUrl;
const userName = userResults.formattedName;
const email = userResults.emailAddress;
// Create a Firebase account and get the Custom Auth Token.
const firebaseToken = await createFirebaseAccount(linkedInUserID, userName, profilePic, email, accessToken);
// Serve an HTML page that signs the user in and updates the user profile.
res.jsonp({
token: firebaseToken,
});
});
});
});
} catch (error) {
return res.jsonp({ error: error.toString });
}
});
/**
* Creates a Firebase account with the given user profile and returns a custom auth token allowing
* signing-in this account.
* Also saves the accessToken to the datastore at /linkedInAccessToken/$uid
*
* @returns {Promise<string>} The Firebase custom auth token in a promise.
*/
async function createFirebaseAccount(linkedinID, displayName, photoURL, email, accessToken) {
// The UID we'll assign to the user.
const uid = `linkedin:${linkedinID}`;
// Save the access token tot he Firebase Realtime Database.
const databaseTask = admin.database().ref(`/linkedInAccessToken/${uid}`).set(accessToken);
// Create or update the user account.
const userCreationTask = admin.auth().updateUser(uid, {
displayName: displayName,
photoURL: photoURL,
email: email,
emailVerified: true,
}).catch((error) => {
// If user does not exists we create it.
if (error.code === 'auth/user-not-found') {
return admin.auth().createUser({
uid: uid,
displayName: displayName,
photoURL: photoURL,
email: email,
emailVerified: true,
});
}
throw error;
});
// Wait for all async task to complete then generate and return a custom auth token.
await Promise.all([userCreationTask, databaseTask]);
// Create a Firebase custom auth token.
const token = await admin.auth().createCustomToken(uid);
functions.logger.log(
'Created Custom token for UID "',
uid,
'" Token:',
token
);
return token;
}