UNPKG

line-login

Version:

SDK to use LINE Login in express based node.js application

347 lines (309 loc) 12.1 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>JSDoc: Source: line-login.js</title> <script src="scripts/prettify/prettify.js"> </script> <script src="scripts/prettify/lang-css.js"> </script> <!--[if lt IE 9]> <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css"> <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css"> </head> <body> <div id="main"> <h1 class="page-title">Source: line-login.js</h1> <section> <article> <pre class="prettyprint source linenums"><code>"use strict"; const debug = require("debug")("line-login:module"); const request = require("request"); const jwt = require("jsonwebtoken"); const secure_compare = require("secure-compare"); const crypto = require("crypto"); const api_version = "v2.1"; Promise = require("bluebird"); Promise.promisifyAll(request); /** @class */ class LineLogin { /** @constructor @param {Object} options @param {String} options.channel_id - LINE Channel Id @param {String} options.channel_secret - LINE Channel secret @param {String} options.callback_url - LINE Callback URL @param {String} [options.scope="profile openid"] - Permission to ask user to approve. Supported values are "profile", "openid" and "email". To specify email, you need to request approval to LINE. @param {String} [options.prompt] - Used to force the consent screen to be displayed even if the user has already granted all requested permissions. Supported value is "concent". @param {string} [options.bot_prompt="normal"] - Displays an option to add a bot as a friend during login. Set value to either normal or aggressive. Supported values are "normal" and "aggressive". @param {Boolean} [options.verify_id_token=true] - Used to verify id token in token response. Default is true. */ constructor(options){ const required_params = ["channel_id", "channel_secret", "callback_url"]; const optional_params = ["scope", "prompt", "bot_prompt", "session_options", "verify_id_token"]; // Check if required parameters are all set. required_params.map((param) => { if (!options[param]){ throw new Error(`Required parameter ${param} is missing.`); } }) // Check if configured parameters are all valid. Object.keys(options).map((param) => { if (!required_params.includes(param) &amp;&amp; !optional_params.includes(param)){ throw new Error(`${param} is not a valid parameter.`); } }) this.channel_id = options.channel_id; this.channel_secret = options.channel_secret; this.callback_url = options.callback_url; this.scope = options.scope || "profile openid"; this.prompt = options.prompt; this.bot_prompt = options.bot_prompt || "normal"; if (typeof options.verify_id_token === "undefined"){ this.verify_id_token = true; } else { this.verify_id_token = options.verify_id_token; } } /** Middlware to initiate OAuth2 flow by redirecting user to LINE authorization endpoint. Mount this middleware to the path you like to initiate authorization. @method @return {Function} */ auth(){ return (req, res, next) => { let state = req.session.line_login_state = LineLogin._random(); let nonce = req.session.line_login_nonce = LineLogin._random(); let url = this.make_auth_url(state, nonce); return res.redirect(url); } } /** Middleware to handle callback after authorization. Mount this middleware to the path corresponding to the value of Callback URL in LINE Developers Console. @method @param {Function} s - Callback function on success. @param {Function} f - Callback function on failure. */ callback(s, f){ return (req, res, next) => { const code = req.query.code; const state = req.query.state; const friendship_status_changed = req.query.friendship_status_changed; if (!code){ debug("Authorization failed."); return f(new Error("Authorization failed.")); } if (!secure_compare(req.session.line_login_state, state)){ debug("Authorization failed. State does not match."); return f(new Error("Authorization failed. State does not match.")); } debug("Authorization succeeded."); this.issue_access_token(code).then((token_response) => { if (this.verify_id_token &amp;&amp; token_response.id_token){ let decoded_id_token; try { decoded_id_token = jwt.verify( token_response.id_token, this.channel_secret, { audience: this.channel_id, issuer: "https://access.line.me", algorithms: ["HS256"] } ); if (!secure_compare(decoded_id_token.nonce, req.session.line_login_nonce)){ throw new Error("Nonce does not match."); } debug("id token verification succeeded."); token_response.id_token = decoded_id_token; } catch(exception) { debug("id token verification failed."); if (f) return f(req, res, next, new Error("Verification of id token failed.")); throw new Error("Verification of id token failed."); } } s(req, res, next, token_response); }).catch((error) => { debug(error); if (f) return f(req, res, next, error); throw error; }); } } /** Method to make authorization URL @method @param {String} [nonce] - A string used to prevent replay attacks. This value is returned in an ID token. @return {String} */ make_auth_url(state, nonce){ const client_id = encodeURIComponent(this.channel_id); const redirect_uri = encodeURIComponent(this.callback_url); const scope = encodeURIComponent(this.scope); const prompt = encodeURIComponent(this.prompt); const bot_prompt = encodeURIComponent(this.bot_prompt); let url = `https://access.line.me/oauth2/${api_version}/authorize?response_type=code&amp;client_id=${client_id}&amp;redirect_uri=${redirect_uri}&amp;scope=${scope}&amp;bot_prompt=${bot_prompt}&amp;state=${state}`; if (this.prompt) url += `&amp;prompt=${encodeURIComponent(this.prompt)}`; if (nonce) url += `&amp;nonce=${encodeURIComponent(nonce)}`; return url } /** Method to retrieve access token using authorization code. @method @param {String} code - Authorization code @return {Object} */ issue_access_token(code){ const url = `https://api.line.me/oauth2/${api_version}/token`; const form = { grant_type: "authorization_code", code: code, redirect_uri: this.callback_url, client_id: this.channel_id, client_secret: this.channel_secret } return request.postAsync({ url: url, form: form }).then((response) => { if (response.statusCode == 200){ return JSON.parse(response.body); } return Promise.reject(new Error(response.statusMessage)); }); } /** Method to verify the access token. @method @param {String} access_token - Access token @return {Object} */ verify_access_token(access_token){ const url = `https://api.line.me/oauth2/${api_version}/verify?access_token=${encodeURIComponent(access_token)}`; return request.getAsync({ url: url }).then((response) => { if (response.statusCode == 200){ return JSON.parse(response.body); } return Promise.reject(new Error(response.statusMessage)); }); } /** Method to get a new access token using a refresh token. @method @param {String} refresh_token - Refresh token. @return {Object} */ refresh_access_token(refresh_token){ const url = `https://api.line.me/oauth2/${api_version}/token`; const form = { grant_type: "refresh_token", refresh_token: refresh_token, client_id: this.channel_id, client_secret: this.channel_secret } return request.postAsync({ url: url, form: form }).then((response) => { if (response.statusCode == 200){ return JSON.parse(response.body); } return Promise.reject(new Error(response.statusMessage)); }); } /** Method to invalidate the access token. @method @param {String} access_token - Access token. @return {Null} */ revoke_access_token(access_token){ const url = `https://api.line.me/oauth2/${api_version}/revoke`; const form = { access_token: access_token, client_id: this.channel_id, client_secret: this.channel_secret } return request.postAsync({ url: url, form: form }).then((response) => { if (response.statusCode == 200){ return null; } return Promise.reject(new Error(response.statusMessage)); }); } /** Method to get user's display name, profile image, and status message. @method @param {String} access_token - Access token. @return {Object} */ get_user_profile(access_token){ const url = `https://api.line.me/v2/profile`; const headers = { Authorization: "Bearer " + access_token } return request.getAsync({ url: url, headers: headers }).then((response) => { if (response.statusCode == 200){ return JSON.parse(response.body); } return Promise.reject(new Error(response.statusMessage)); }); } /** Method to get the friendship status of the user and the bot linked to your LNIE Login channel. @method @param {String} access_token - Access token. @return {Object} */ get_friendship_status(access_token){ const url = `https://api.line.me/friendship/v1/status`; const headers = { Authorization: "Bearer " + access_token } return request.getAsync({ url: url, headers: headers }).then((response) => { if (response.statusCode == 200){ return JSON.parse(response.body); } return Promise.reject(new Error(response.statusMessage)); }); } /** Method to generate random string. @method @return {Number} */ static _random(){ return crypto.randomBytes(20).toString('hex'); } } module.exports = LineLogin; </code></pre> </article> </section> </div> <nav> <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="LineLogin.html">LineLogin</a></li></ul> </nav> <br class="clear"> <footer> Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Wed Mar 14 2018 13:39:58 GMT+0900 (JST) </footer> <script> prettyPrint(); </script> <script src="scripts/linenumber.js"> </script> </body> </html>