diff --git a/.gitignore b/.gitignore index dedbee2..a286a33 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ *.ppk *.glue firebase.json +config.js .DS_Store diff --git a/core/endpoints.js b/core/endpoints.js index c8aa202..3e2ece7 100644 --- a/core/endpoints.js +++ b/core/endpoints.js @@ -10,7 +10,7 @@ var endpoints = { sendVerificationEmailUrl: "https://www.googleapis.com/identitytoolkit/v3/relyingparty/getOobConfirmationCode?key={0}", verifyEmailUrl: "https://www.googleapis.com/identitytoolkit/v3/relyingparty/setAccountInfo?key={0}", sendPasswordResetEmailUrl: "https://www.googleapis.com/identitytoolkit/v3/relyingparty/getOobConfirmationCode?key={0}", - verifyPasswordResetcodeUrl: "https://www.googleapis.com/identitytoolkit/v3/relyingparty/resetPassword?key={0", + deleteAccountUrl: "https://www.googleapis.com/identitytoolkit/v3/relyingparty/deleteAccount?key={0}", resetPasswordUrl: "https://www.googleapis.com/identitytoolkit/v3/relyingparty/resetPassword?key={0}", changePasswordUrl: "https://www.googleapis.com/identitytoolkit/v3/relyingparty/setAccountInfo?key={0}", accountInfoUrl:"https://www.googleapis.com/identitytoolkit/v3/relyingparty/getAccountInfo?key={0}", @@ -25,7 +25,7 @@ endpoints.getSignUpUrl = (apiKey) => endpoints.signUpUrl.format(apiKey); endpoints.getsendVerificationEmailUrl = (apiKey) => endpoints.sendVerificationEmailUrl.format(apiKey); endpoints.getverifyEmailUrl = (apiKey) => endpoints.verifyEmailUrl.format(apiKey); endpoints.getsendPasswordResetEmailUrl = (apiKey) => endpoints.sendPasswordResetEmailUrl.format(apiKey); -endpoints.getverifyPasswordResetcodeUrl = (apiKey) => endpoints.verifyPasswordResetcodeUrl.format(apiKey); +endpoints.getDeleteAccountUrl = (apiKey) => endpoints.deleteAccountUrl.format(apiKey); endpoints.getresetPasswordUrl = (apiKey) => endpoints.resetPasswordUrl.format(apiKey); endpoints.getchangePasswordUrl = (apiKey) => endpoints.changePasswordUrl.format(apiKey); endpoints.getAccountInfoUrl = (apiKey) => endpoints.accountInfoUrl.format(apiKey); diff --git a/core/instagram.js b/core/instagram.js new file mode 100644 index 0000000..938a2c3 --- /dev/null +++ b/core/instagram.js @@ -0,0 +1,86 @@ +/** + * Creates a Firebase account with the given user profile and returns a custom auth token allowing + * signing-in this account. + * + * @returns {Promise} The Firebase custom auth token in a promise. + */ + + var admin = require("firebase-admin"); + exports.init = function (serviceAccount) { + admin.initializeApp({ + credential: admin.credential.cert(serviceAccount), + databaseURL: 'https://' + serviceAccount.project_id + '.firebaseio.com' + }); +}; + +exports.logInstagramUserIntoFirebase = function(instagramID, displayName, photoURL, callback) { + if (!admin){ + callback('Not initialized'); + return; + } + + // The UID we'll assign to the user. + const uid = 'instagram:' + instagramID; + console.log('this is the user uid: ', uid) + + // Create or update the user account. + var userInfo = { + displayName: displayName, + photoURL: photoURL + }; + console.log('this is the user info: ', userInfo) + + updateFirebaseUserOrCreateNewUser(uid, userInfo, function(err){ + if (err){ + console.log('this is the UPDATE ERROR: ', err) + callback(err); + return; + } + else{ + const token = admin.auth().createCustomToken(uid); + console.log('this is the your TOKEN: ', token) + callback(token); + } + }); +}; + +function updateFirebaseUserOrCreateNewUser(uid, userInfo, callback) { + admin.auth().updateUser(uid, userInfo) + .then(function(userRecord){ + callback(null); + }) + .catch(function(error){ + if (error.code === 'auth/user-not-found') { + createFirebaseUser(uid, userInfo, callback); + } + else { + callback(error); + } + }); +} + +function createFirebaseUser(uid, userInfo, callback){ + userInfo.uid = uid; + admin.auth().createUser(userInfo) + .then(function(userRecord){ + callback(null); + }) + .catch(function(error){ + callback(error); + }) +} + +// function createCustomToken(uid, callback){ +// uid = uid; +// admin.auth().createCustomToken(uid) +// .then(function(token){ +// console.log('this is the your TOKEN: ', token) +// callback(token); +// }) +// .catch(function(error){ +// console.log('this is the user error: ', error) + +// callback(error); +// }) +// } +// } \ No newline at end of file diff --git a/index.js b/index.js index 6473284..5fd2f52 100644 --- a/index.js +++ b/index.js @@ -3,6 +3,8 @@ const emailPasswordProvider = require('./providers/email-password-provider'); const socialProviders = require('./providers/social-providers'); const account = require('./user/account'); +const instagram = require('./providers/insta_redirect'); + function firebaseAuth(apiKey){ if (typeof(apiKey) !== 'string' || apiKey.trim().length === 0) @@ -56,8 +58,12 @@ firebaseAuth.prototype.refreshToken = function(refreshToken, callback) { account.refreshToken(this.apiKey, refreshToken, callback); }; -firebaseAuth.prototype.registerWithEmail = function(email, password, extras, callback) { - emailPasswordProvider.register(this.apiKey, email, password, extras, callback); +firebaseAuth.prototype.deleteAccount = function(token, callback) { + account.deleteAccount(this.apiKey, token, callback); +}; + +firebaseAuth.prototype.registerWithEmail = function(email, password, name, photoUrl, callback) { + emailPasswordProvider.register(this.apiKey, email, password, name, photoUrl, callback); }; firebaseAuth.prototype.loginWithProviderID = function(providerToken, providerId, callback) { @@ -80,4 +86,8 @@ firebaseAuth.prototype.loginWithTwitter = function(providerToken, callback) { socialProviders.loginWithTwitter(this.apiKey, providerToken, callback); }; +firebaseAuth.prototype.processInstagramAuthCode = function(serviceAccount, instagramAuthCode, redirectUri, callback){ + instagram.processInstagramAuthCode(serviceAccount, instagramAuthCode, redirectUri, callback); +}; + module.exports = firebaseAuth; \ No newline at end of file diff --git a/middlewares/protector.js b/middlewares/protector.js index e90564e..d01c6dc 100644 --- a/middlewares/protector.js +++ b/middlewares/protector.js @@ -1,4 +1,4 @@ -'use strict' +'use strict'; const user = require('../models/firebase-user'); const validator = require('validator'); diff --git a/package.json b/package.json index 692517b..7e7a981 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "author": "Rudigo", "name": "firebaseauth", - "version": "0.1.0", + "version": "0.1.1", "description": "Firebase authentication library - a node js wrapper around the Firebase REST API. It can generate Firebase auth token based on given email-password combination or OAuth token (issued by Google, Facebook, Twitter or Github). This Firebase token can then be used with REST queries against Firebase Database endpoints or for protecting resources on a server.", "main": "index.js", "scripts": { @@ -12,10 +12,11 @@ }, "license": "ISC", "dependencies": { - "firebase-admin": "^5.2.1", + "firebase-admin": "^5.4.1", "node-rest-client": "^3.1.0", "request": "^2.81.0", "request-promise": "^4.2.1", + "simple-oauth2": "^1.3.0", "string-format": "^0.5.0", "validator": "^8.0.0" } diff --git a/providers/email-password-provider.js b/providers/email-password-provider.js index bdedd71..e4f908d 100644 --- a/providers/email-password-provider.js +++ b/providers/email-password-provider.js @@ -278,8 +278,7 @@ exports.resetPassword = function(apiKey, oobCode, newPassword, callback){ var error = utils.processFirebaseError(err); callback(error);     }); - -} +}; exports.changePassword = function(apiKey, token, password, callback){ if (typeof(callback) !== 'function'){ @@ -291,7 +290,7 @@ exports.changePassword = function(apiKey, token, password, callback){ password: password, idToken: token, returnSecureToken: true - } + }; if (!validator.isLength(password, {min: 6})){ callback(utils.invalidArgumentError('Password. Password must be at least 6 characters')); diff --git a/providers/insta_redirect.js b/providers/insta_redirect.js new file mode 100644 index 0000000..c9c70a9 --- /dev/null +++ b/providers/insta_redirect.js @@ -0,0 +1,92 @@ +'use strict'; + +const insta = require('../core/instagram'); + +// Instagram OAuth 2 setup +// const credentials = { +// client: { +// id: config.instagram.clientId, +// secret: config.instagram.clientSecret +// }, +// auth: { +// tokenHost: 'https://api.instagram.com', +// tokenPath: '/oauth/access_token' +// } +// }; + +// var oauth2; +// exports.init = function (credentials) { +// oauth2 = require('simple-oauth2').create(credentials); +// }; + +// const OAUTH_REDIRECT_PATH = '/redirect'; +// const OAUTH_CALLBACK_PATH = '/instagram-callback'; +// const OAUTH_MOBILE_CALLBACK_PATH = '/instagram-mobile-callback'; +// const OAUTH_CODE_EXCHANGE_PATH = '/instagram-mobile-exchange-code'; + +// Custom URI scheme for Android and iOS apps. +// const APP_CUSTOM_SCHEME = 'instagram-sign-in-demo'; + +// Instagram scopes requested. +// const OAUTH_SCOPES = 'basic'; + +/** + * Exchanges a given Instagram auth code passed in the 'code' URL query parameter for a Firebase auth token. + */ + //d6ae06a0ee134e9ea7c485c449e8d157 +exports.processInstagramAuthCode = function(credentials, serviceAccount, instagramAuthCode, redirectUri, callback) { + const oauth2 = require('simple-oauth2').create(credentials); + + const oauthParams = { + code: instagramAuthCode, + redirect_uri: redirectUri + }; + + + oauth2.authorizationCode.getToken(oauthParams) + .then(function (results) { + console.log('Auth code exchange result received:', results); + // We have an Instagram access token and the user identity now. + 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. + insta.init(serviceAccount); + insta.logInstagramUserIntoFirebase(instagramUserID, userName, profilePic, callback); + }) + .catch(function (error) { + console.log('Auth code exchange error received:', error); + }); +}; + +/** + * Passes the auth code to your Mobile application by redirecting to a custom scheme URL. This serves as a fallback in + * case App Link/Universal Links are not supported on the device. + * Native Mobile apps should use this callback. + */ +// exports.handleMobileRedirect = function(req, res){ +// res.redirect(APP_CUSTOM_SCHEME + ':/' + OAUTH_CALLBACK_PATH + '?' + req.originalUrl.split + '?' + [1]); +// }; + +/** + * Exchanges a given Instagram auth code passed in the 'code' URL query parameter for a Firebase auth token and returns + * a Firebase Custom Auth token, Instagram access token and user identity as a JSON object. + * This endpoint is meant to be used by native mobile clients only since no Session Fixation attacks checks are done. + */ +// exports.handleTokenRedirect = function (req, res){ +// console.log('Received auth code:', req.query.code); +// oauth2.authCode.getToken({ +// code: req.query.code, +// redirect_uri: req.protocol + '://' + req.get('host') + OAUTH_MOBILE_CALLBACK_PATH +// }).then(function (results){ +// console.log('Auth code exchange result received:', results); + +// // Create a Firebase Account and get the custom Auth Token. +// insta.createFirebaseAccount(results.user.id, results.user.full_name, results.user.profile_picture, firebaseToken) +// .then(function (firebaseToken){ +// // Send the custom token, access token and profile data as a JSON object. +// res.send(firebaseToken); +// }); +// }); +// }; \ No newline at end of file diff --git a/providers/social-providers.js b/providers/social-providers.js index a109191..5107cb6 100644 --- a/providers/social-providers.js +++ b/providers/social-providers.js @@ -1,4 +1,4 @@ -'use strict' +'use strict'; const utils = require('../core/utils'); const endpoints = require('../core/endpoints'); @@ -8,7 +8,7 @@ const ids = { Google: "google.com", Github: "github.com", Twitter: "twitter.com" -} +}; function loginWithProviderID(apiKey, providerToken, providerId, callback){ if (typeof(callback) !== 'function'){ @@ -38,7 +38,7 @@ function loginWithProviderID(apiKey, providerToken, providerId, callback){ requestUri: "http://localhost", returnSecureToken: true, returnIdpCredential: true - } + }; var signInEndpoint = endpoints.getSocialIdentityUrl(apiKey); endpoints.post(signInEndpoint, payload) @@ -59,16 +59,16 @@ exports.loginWithProviderID = loginWithProviderID; exports.loginWithFacebook = function (apiKey, providerToken, callback){ loginWithProviderID(apiKey, providerToken, ids.Facebook, callback) -} +}; exports.loginWithGoogle = function (apiKey, providerToken, callback){ loginWithProviderID(apiKey, providerToken, ids.Google, callback) -} +}; exports.loginWithGithub = function (apiKey, providerToken, callback){ loginWithProviderID(apiKey, providerToken, ids.Github, callback) -} +}; exports.loginWithTwitter = function (apiKey, providerToken, callback){ loginWithProviderID(apiKey, providerToken, ids.Twitter, callback) -} \ No newline at end of file +}; \ No newline at end of file diff --git a/user/account.js b/user/account.js index fef9db0..79a2feb 100644 --- a/user/account.js +++ b/user/account.js @@ -30,7 +30,7 @@ exports.getProfile = function(apiKey, token, callback){ var error = utils.processFirebaseError(err); callback(error); }) -} +}; exports.updateProfile = function(apiKey, token, name, photoUrl, callback){ if (photoUrl && typeof(photoUrl) === 'function'){ @@ -105,4 +105,31 @@ exports.refreshToken = function(apiKey, refreshToken, callback) { var error = utils.processFirebaseError(err); callback(error); }) +} + +exports.deleteAccount = function(apiKey, token, callback) { + if (typeof(callback) !== 'function'){ + throw new Error('No valid callback function defined'); + return; + } + + if (typeof(token) !== 'string' || token.trim().length === 0){ + callback(utils.invalidArgumentError('Token')); + return; + } + + var payload = { + idToken: token, + } + + var deleteAccountEndpoint = endpoints.getDeleteAccountUrl(apiKey); + endpoints.post(deleteAccountEndpoint, payload) + .then(function(userInfo){ + var authResult = ({status: "SUCCESS"}); + callback(null, authResult); + }) + .catch(function(err){ + var error = utils.processFirebaseError(err); + callback(error); + }) } \ No newline at end of file