This repository was archived by the owner on May 19, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 96
Expand file tree
/
Copy pathapp.js
More file actions
161 lines (143 loc) · 5.43 KB
/
app.js
File metadata and controls
161 lines (143 loc) · 5.43 KB
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
// Copyright 2015-2016, Google, Inc.
//
// 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.
// [START app]
'use strict';
// Modules imports
const rp = require('request-promise');
const express = require('express');
const bodyParser = require('body-parser');
// Firebase Setup
const admin = require('firebase-admin');
const serviceAccount = require('./service-account.json');
admin.initializeApp({
credential: admin.credential.cert(serviceAccount)
});
// Load config file
const config = require('./config.json');
// Generate a Request option to access LINE APIs
function generateLineApiRequest(apiEndpoint, lineAccessToken) {
return {
method: 'GET',
url: `${apiEndpoint}?access_token=${lineAccessToken}`,
json: true
};
}
function lineProfileRequest(apiEndpoint, lineAccessToken) {
return {
method: 'GET',
url: `${apiEndpoint}`,
headers: {'Authorization': `Bearer ${lineAccessToken}`},
json: true
}
}
/**
* Look up Firebase user based on LINE's mid. If the Firebase user does not exist,
+ fetch LINE profile and create a new Firebase user with it.
*
* @returns {Promise<UserRecord>} The Firebase user record in a promise.
*/
function getFirebaseUser(lineAccessToken) {
const getProfileOptions = lineProfileRequest('https://api.line.me/v2/profile', lineAccessToken);
console.log(getProfileOptions)
return request(getProfileOptions).then(response => {
const displayName = response.displayName;
const firebaseUid = `line:${response.userId}`;
return firebaseAdmin.auth().getUser(firebaseUid).catch(err => {
if (err.code === 'auth/user-not-found') {
return firebaseAdmin.auth().createUser({
provider: 'LINE',
uid: firebaseUid,
displayName: displayName
})
}
throw err
})
})
}
/**
* Verify LINE access token and return a custom auth token allowing signing-in
* the corresponding Firebase account.
*
* Here are the steps involved:
* 1. Verify with LINE server that a LINE access token is valid
* 2. Check if a Firebase user corresponding to the LINE user already existed.
* If not, fetch user profile from LINE and generate a corresponding Firebase user.
* 3. Return a custom auth token allowing signing-in the Firebase account.
*
* @returns {Promise<string>} The Firebase custom auth token in a promise.
*/
function verifyLineToken(lineAccessToken) {
// Send request to LINE server for access token verification
const verifyTokenOptions = generateLineApiRequest('https://api.line.me/oauth2/v2.1/verify', lineAccessToken);
var firebaseUid = '';
// STEP 1: Verify with LINE server that a LINE access token is valid
return rp(verifyTokenOptions)
.then(response => {
// Verify the token’s channelId match with my channelId to prevent spoof attack
// <IMPORTANT> As LINE's Get user profiles API response doesn't include channelID,
// you must not skip this step to make sure that the LINE access token is indeed
// issued for your channel.
//TODO: consider !== here
if (response.client_id != config.line.channelId)
return Promise.reject(new Error('LINE channel ID mismatched'));
// STEP 2: Access token validation succeeded, so look up the corresponding Firebase user
return getFirebaseUser(lineAccessToken);
})
.then(userRecord => {
// STEP 3: Generate Firebase Custom Auth Token
const tokenPromise = admin.auth().createCustomToken(userRecord.uid,{provider: 'LINE'});
tokenPromise.then(token => {
console.log('Created Custom token for UID "', userRecord.uid, '" Token:', token);
});
return tokenPromise;
});
}
// ExpressJS setup
const app = express();
app.use(bodyParser.json());
// Verify LINE token and exchange for Firebase Custom Auth token
app.post('/verifyToken', (req, res) => {
if (req.body.token === undefined) {
const ret = {
error_message: 'Access Token not found'
};
return res.status(400).send(ret);
}
const reqToken = req.body.token;
// Verify LINE access token with LINE server then generate Firebase Custom Auth token
verifyLineToken(reqToken)
.then(customAuthToken => {
const ret = {
firebase_token: customAuthToken
};
return res.status(200).send(ret);
})
.catch(err => {
// If LINE access token verification failed, return error response to client
const ret = {
error_message: 'Authentication error: Cannot verify access token.'
};
return res.status(403).send(ret);
});
});
// Endpoint to verify if your Node server is up
app.get('/', (req, res) => {
return res.status(200).send('Server is up and running!');
});
// Start the server
const server = app.listen(process.env.PORT || '8080', () => {
console.log('App listening on port %s', server.address().port);
console.log('Press Ctrl+C to quit.');
});
// [END app]