iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 4
0
Modern Web

30天使用Node.js在AWS上開發後端系列 第 6

30-5 Node.js起手式之專案建立與資料庫串接 (2)

上面講的應該有些複雜
我會先把一些code打完, 並附上註解
之後會針對Express這個框架大概說明一下!

既然處理api/v1這個路徑這邊程式碼寫完之後
接著就是api/v1/auth/login/facebook這裡了

先去v1目錄底下新增一個index.js的檔案
v1/index.js

/*jslint node: true */
'use strict';

const express = require('express');
const router = express.Router();
// 將處理auth路徑相關的程式引入
const auth = require('./auth');
// 只要是api/v1/auth開頭的路徑都由auth.js處理
router.use('/auth', auth);

module.exports = router;

這時候我們幫app.js加上一個express-validator這個套件
像在開發的時候, 絕對要小心來自外部的資料
所以透過別人寫好的驗證器模組, 可以快速將惡意的資料格式阻擋
app.js

var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var validator  = require('express-validator');
var index = require('./routes/index');
var users = require('./routes/users');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');

// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/', index);

// 中間省略
module.exports = app;

接下來是v1/auth.js

/*jslint node: true */
'use strict';

const express  = require('express');
const router   = express.Router();
// 使用別人寫好的套件, 可以輕鬆取得FB上使用者的資料
const bluebird = require('bluebird');
const fbgraph  = bluebird.promisifyAll(require('fbgraph'));
const _ = require('underscore');
const jwt = require('jsonwebtoken');
const db = require('../../models').getDatabase();
const USER_FIELD = {
	_id: 1,
	name: 1,
	status: 1,
	img: 1
};
const jwtConfig = {
	// jwt的密鑰, 不可外流
	secret: 'abcdQQWWEEE',
	// 使用哪種演算法產生token
	alg: 'HS512'
};
const config = {
	jwtConfig: jwtConfig,
	accessTokenConfig: {
		algorithm: jwtConfig.alg,
		expiresIn: "30d"
	}
}
// router.post的意思就是指接受http post的方法
router.post('/login/facebook', async (req, res) => {
	console.log('req.body', req.body);
	req.checkBody('socialToken').notEmpty();
	let errors = req.validationErrors();
	// 如果有錯, 就回傳http error code 405
	if (errors) {
		return res.status(405).json({ err: errors });
	}
	let user, userIds;
	// 要從Facebook中取得的欄位
	let fields = { 
		fields: 'email,name,birthday,gender'
	};
	// 設定fbgraph中的使用者取得權杖
	fbgraph.setAccessToken(req.body.socialToken);
	try {
		let fbProfile = await fbgraph.getAsync('me', fields);
		// 如果沒有取得到使用者的生日性別跟名字, 就給個預設值
		if (!fbProfile.birthday) {
			fbProfile.birthday = 0;
		}
		if (!fbProfile.gender) {
			fbProfile.gender = 'unset';
		}
		if (!fbProfile.name) {
			fbProfile.name = 'guest';
		}
		// 設定查詢式
		let query = {
			socialId: 'facebook_' + fbProfile.id
		};

		let user = await db.collection('user').findOne(query, USER_FIELD);
		if (!user) {
			// 如果使用者不存在, 在資料庫新增一筆
			let uConfig = {
				status: 'active',
				name: fbProfile.name,
				email: fbProfile.email || fbProfile.id + '@still.not.set',
				gender: fbProfile.gender,
				img: 'https://graph.facebook.com/' + fbProfile.id + '/picture?type=large',
				// 將字串的生日轉成毫秒數
				birth: Date.parse(fbProfile.birthday),
				// 使用者在Facebook上的ID
				socialId: 'facebook_' + fbProfile.id,
				// 使用者在Facebook上的token
				socialToken: req.body.socialToken,
				// 預留一個欄位, 以後可以擴充為使用帳號密碼
				password: 'not_used',
				// 來自於Facebook, 以後可擴充用
				provider: 'facebook'
			};
			// socialId 必需設定為 unique (不可以重複)
			let opResult = await db.collection('user').insertOne(uConfig, { j: true });
			// 把user指向到uConfig這個物件
			user = uConfig;
			user._id = opResult.insertedId;
		} else if (user.status !== 'active') {
			return res.status(403).json({ err: '此用戶不存在或已被停權' });
		} else {
			// 使用者已經存在
			// 每次使用者登入, 更新其Facebook權杖
			if (req.body.socialToken) {
				await db.collection('user').updateOne({
					_id: user._id
				}, {
					$set: {
						socialToken: req.body.socialToken
					}
				});	
			}
		}
		return res.json(issueTokenWithProfile(user));
	} catch (e) {
		console.error("Logging in using facebook:", e);
		return res.status(500).json({ err: 'Login Err' });
	}
});

// 將使用者部分的資訊放進去token中
function issueTokenWithProfile (user, accessTokenOnly=true) {
	user.password    = undefined;
	user.socialToken = undefined;
	user.socialId    = undefined;
	user.provider    = undefined;
	user.email       = undefined;
	user.status      = undefined;
	let payload = {
		_id: user._id,
		type: 'accessToken',
		status: user.status
	};
	user.accessToken = jwt.sign(payload, config.jwtConfig.secret, config.accessTokenConfig);
	return user;
};
module.exports = router;

下一篇會附上程式碼的壓縮檔 or github
然後大致說明一下 Express 的運作流程


上一篇
30-5 Node.js起手式之專案建立與資料庫串接 (1)
系列文
30天使用Node.js在AWS上開發後端6
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言