-
Notifications
You must be signed in to change notification settings - Fork 3.8k
/
Copy pathindex.js
162 lines (148 loc) · 5.8 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
/**
* 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_REDIRECT_URI = `https://${process.env.GCLOUD_PROJECT}.firebaseapp.com/popup.html`;
const OAUTH_SCOPES = 'basic';
/**
* Creates a configured simple-oauth2 client for Instagram.
*/
function instagramOAuth2Client() {
// Instagram OAuth 2 setup
// TODO: Configure the `instagram.client_id` and `instagram.client_secret` Google Cloud environment variables.
const credentials = {
client: {
id: functions.config().instagram.client_id,
secret: functions.config().instagram.client_secret,
},
auth: {
tokenHost: 'https://api.instagram.com',
tokenPath: '/oauth/access_token',
},
};
return require('simple-oauth2').create(credentials);
}
/**
* Redirects the User to the Instagram authentication consent screen. Also the '__session' cookie is set for later state
* verification.
*/
exports.redirect = functions.https.onRequest((req, res) => {
const oauth2 = instagramOAuth2Client();
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,
});
const redirectUri = oauth2.authorizationCode.authorizeURL({
redirect_uri: OAUTH_REDIRECT_URI,
scope: OAUTH_SCOPES,
state: state,
});
functions.logger.log('Redirecting to:', redirectUri);
res.redirect(redirectUri);
});
});
/**
* Exchanges a given Instagram 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, display name, photo URL and Instagram acces token are sent back in a JSONP callback
* function with function name defined by the 'callback' query parameter.
*/
exports.token = functions.https.onRequest(async (req, res) => {
const oauth2 = instagramOAuth2Client();
try {
return cookieParser()(req, res, async () => {
functions.logger.log('Received verification state:', req.cookies.__session);
functions.logger.log('Received state:', req.query.state);
if (!req.cookies.__session) {
throw new Error('State cookie not set or expired. Maybe you took too long to authorize. Please try again.');
} else if (req.cookies.__session !== req.query.state) {
throw new Error('State validation failed');
}
functions.logger.log('Received auth code:', req.query.code);
const results = await oauth2.authorizationCode.getToken({
code: req.query.code,
redirect_uri: OAUTH_REDIRECT_URI,
});
functions.logger.log('Auth code exchange result received:', results);
// We have an Instagram access token and the user identity now.
const accessToken = results.access_token;
const instagramUserID = results.user.id;
const profilePic = results.user.profile_picture;
const userName = results.user.full_name;
// Create a Firebase account and get the Custom Auth Token.
const firebaseToken = await createFirebaseAccount(instagramUserID, userName, profilePic, accessToken);
// Serve an HTML page that signs the user in and updates the user profile.
return 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 /instagramAccessToken/$uid
*
* @returns {Promise<string>} The Firebase custom auth token in a promise.
*/
async function createFirebaseAccount(instagramID, displayName, photoURL, accessToken) {
// The UID we'll assign to the user.
const uid = `instagram:${instagramID}`;
// Save the access token to the Firebase Realtime Database.
const databaseTask = admin.database().ref(`/instagramAccessToken/${uid}`).set(accessToken);
// Create or update the user account.
const userCreationTask = admin.auth().updateUser(uid, {
displayName: displayName,
photoURL: photoURL,
}).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,
});
}
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;
}