Skip to content

Webschool-io/Atomic-Module-Passport-API

Repository files navigation

Atomic Module - Passport API

Módulo de login para o Passport utilizando nossa arquitetura atômica do Be MEAN.

Configuração

Para utilizar algum sistema de login externo, como Facebook e Github você deverá criar um arquivo .env na raíz do projeto com essas informações:

facebookAuth_clientID=xxxxxx_YOUR_FACEBOOK_clientID
facebookAuth_clientSecret=xxxxxx_YOUR_FACEBOOK_clientSecret
githubAuth_clientID=xxxxxx_YOUR_GITHUB_clientID
githubAuth_clientSecret=xxxxxx_YOUR_GITHUB_clientSecret

Rotas

  • GET /api/users/: Lista usuários
    • necessita estar loggado
  • POST /api/users/: Cria um usuário
    • name
    • email
    • password
  • POST /api/users/login: Entra com o usuário
    • email
    • password
  • GET /api/users/logout: Sai com o usuário
    • necessita estar loggado

Como foi feito

Começamos a refatorar pelo model.js separando o Schema do Model, deixando o schema.js assim:

// schema.js
const mongoose = require('mongoose');
const bcrypt = require('bcrypt-nodejs');

const Schema = mongoose.Schema;
const Molecule = {
  local: {
    email: String,
    password: String
  },
  facebook: {
    id: String,
    token: String,
    email: String,
    name: String
  },
  twitter: {
    id: String,
    token: String,
    displayName: String,
    username: String
  },
  google: {
    id: String,
    token: String,
    email: String,
    name: String
  },
  github: {
    id: String,
    token: String,
    email: String,
    name: String
  }
}

//define the schema for our user model
const userSchema = mongoose.Schema(Molecule);

//methods ========================
//generation a hash
userSchema.methods.generateHash = function (password) {
  return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null);
};

// checking if a password is valid
userSchema.methods.validPassword = function (password) {
  return bcrypt.compareSync(password, this.local.password);
};

module.exports = userSchema;

E o model.js assim:

const mongoose  = require('mongoose');
const userSchema = require('./schema');

module.exports = mongoose.model('User', userSchema);

Próximo passo é atomizar esse Schema separando em moléculas menores:

// molecule
const mongoose = require('mongoose');
const bcrypt = require('bcrypt-nodejs');

const Schema = mongoose.Schema;
const Molecule = {
  local: require('./molecule-local'),
  facebook: require('./molecule-facebook'),
  twitter: require('./molecule-twitter'),
  google: require('./molecule-google'),
  github: require('./molecule-github')
}

//define the schema for our user model
const userSchema = mongoose.Schema(Molecule);

//methods ========================
//generation a hash
userSchema.methods.generateHash = function(password) {
    return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null);
};

// checking if a password is valid
userSchema.methods.validPassword = function(password) {
    return bcrypt.compareSync(password, this.local.password);
};

module.exports = userSchema;

Onde cada tipo de molécula é 1 tipo diferente de login:

// molecule-local
module.exports = {
  email: String,
  password: String
}
// molecule-facebook
module.exports = {
  id: String,
  token: String,
  email: String,
  name: String
}
// molecule-twitter
module.exports = {
  id: String,
  token: String,
  displayName: String,
  username: String
}
// molecule-google
module.exports = {
  id: String,
  token: String,
  email: String,
  name: String
}
// molecule-github
module.exports = {
  id: String,
  token: String,
  displayName: String,
  username: String
}

Após essa separação vamos separar as estratégias de login em suas Organelles responsáveis, por exemplo:

  • organelle-passport-local-login
  • organelle-passport-local-signup
  • organelle-passport-facebook
  • organelle-passport-github

Porém antes disso iremos separar 2 Quarks do Passport que estão nessa parte:

  //used to serialize the user for the session
  passport.serializeUser(function(user, done){
    done(null, user.id);
  });

  //used to deserialize the user
  passport.deserializeUser(function(id, done){
    User.findById(id, function(err, user){
    done(err, user);
    });
  });

Separamos cada callback em um Quark diferente:

// quark-passportSerializeUser
module.exports = () => (user, done) => done(null, user.id)
// quark-passportDeserializeUser
module.exports = (Model) => (id, done) => Model.findById(id, (err, user) => done(err, user))

Esse último Quark ficou bem estranho não? Muita flechinha => hahhahahahhaa mas vou explicar para você como eu cheguei nessa função, vamos analisar ela como estava antes;

passport.deserializeUser(function(id, done){
  User.findById(id, function(err, user){
  done(err, user);
  });
});

Então quando passamos essa função para um módulo ela fica assim:

module.exports = function (Model) { 
  return function (id, done)  { 
    Model.findById(id, function(err, user) {
       done(err, user)
    })
  }
}

Pois devemos exporta a função de callback, porém ela necessita do Model do User, que aqui chamamos apenas de Model para deixar o código mais genérico possível, deve ter percebido também que na função original existem returns pois as funções são assíncronas, mas isso não quer dizer que não podemos usar, deixando o código fica assim:

module.exports = function (Model) { 
  return function (id, done)  { 
    return Model.findById(id, function(err, user) {
       return done(err, user)
    })
  }
}

Pronto! Agora basta passar para arrow function:

module.exports = (Model) => { 
  return  (id, done) => { 
    return Model.findById(id, (err, user) => {
       return done(err, user)
    })
  }
}

E como sabemos que se a função retornar algo diretamente podemos suprimir o return e as { } deixando apenas assim:

module.exports = (Model) => (id, done) => Model.findById(id, (err, user) => done(err, user)

Legal né?

Agora vamos ver como fica nosso código do config/passport.js:

// load up the user model
const Model = require('../modules/User/model');

//load the auth variables
const configAuth = require('../modules/User/atomic/hadrons/hadron-authPassport');

// expose this function to our app using module.exports
module.exports = function(passport){

  //used to serialize the user for the session
  const serializeUser = require('../modules/User/atomic/quarks/quark-passportSerializeUser')();
  passport.serializeUser(serializeUser);

  //used to deserialize the user
  const deserializeUser = require('../modules/User/atomic/quarks/quark-passportDeserializeUser')(Model);
  passport.deserializeUser(deserializeUser);

  const localSignup = require('../modules/User/atomic/organism/organelles/organelle-passport-local-signup')(Model)
  passport.use('local-signup', localSignup)

  // LOCAL
  const localLogin = require('../modules/User/atomic/organism/organelles/organelle-passport-local-login')(Model)
  passport.use('local-login', localLogin);

  // FACEBOOK
  const facebookLogin = require('../modules/User/atomic/organism/organelles/organelle-passport-facebook')(Model)
  passport.use(facebookLogin);

  // GITHUB
  const githubLogin = require('../modules/User/atomic/organism/organelles/organelle-passport-github')(Model)
  passport.use(githubLogin);

};

LISTAR AS ORGANELLES AQUI COM LINK PRO ARQUIVO

Além disso modularizei o configAuth para seu Hadron responsável, pois como sabemos o Hadron é apenas uma estrutura, na nossa arquitetura que compõe 2 ou mais Quarks.

//load the auth variables
const configAuth = require('../modules/User/atomic/hadrons/hadron-authPassport');

Deixando o hadrons/hadron-authPassport assim:

module.exports = {
  'facebookAuth'      :{
      'clientID'      : process.env.facebookAuth_clientID
    , 'clientSecret'  : process.env.facebookAuth_clientSecret
    , 'callbackURL'   : 'http://localhost:8080/users/auth/facebook/callback'
  }
  ,'twitterAuth'        : {
      'consumerKey'     : 'your-consumer-key-here'
    , 'consumerSecret'  : 'your-client-secret-here'
    , 'callbackURL'     : 'http://localhost:8080/auth/twitter/callback'
  }
  ,'googleAuth'       :{
      'clientID'      : 'your-secret-clientID-here'
    , 'clientSecret'  : 'your-client-secret-here'
    , 'callbackURL'   :'http://localhost:8080/auth/google/callback'
  }
  ,'githubAuth': {
      'clientID'      : process.env.githubAuth_clientID
    , 'clientSecret'  : process.env.githubAuth_clientSecret
    , 'callbackURL'   : 'http://localhost:8080/users/auth/github/callback'
  }
};

Você deve estar se perguntando de onde vem esse process.env né?

Então, eu criei um arquivo .env na raíz do projeto contendo a seguinte estrutura:

facebookAuth_clientID=COLOQUE_SUA_CHAVE_AQUI
facebookAuth_clientSecret=COLOQUE_SUA_CHAVE_AQUI
githubAuth_clientID=COLOQUE_SUA_CHAVE_AQUI
githubAuth_clientSecret=COLOQUE_SUA_CHAVE_AQUI

E no nosso arquivo principal do servidor, nesse caso server.js, só precisamos adicionar a seguinte linha no seu início:

require('dotenv').config();

Que ele automagicamente irá colocar os valores contidos do .env na global process.env, com isso você poderá utilizar esses valores em qualquer arquivo e poderá deixar toda informação sensível nesse arquivo o qual deve sempre ser colocado no .gitignore.

Agora nosso código já está BEM MELHOR porém nós ainda precisamos refatorar nosso Model de User que virará organism/organism-user.js assim:

const mongoose  = require('mongoose');
const Molecule = require('../molecules/molecule-user.js');

module.exports = mongoose.model('User', Molecule);

E como você deve ter percebido nós precisamos refatorar o Schema para molecules/molecule-user.js:

const mongoose = require('mongoose');
const bcrypt = require('bcrypt-nodejs');

//define the schema for our user model
const userSchema = mongoose.Schema({
    local: {
        email: String,
        password: String
    },
    facebook: {
        id: String,
        token: String,
        email: String,
        name: String
    },
    twitter: {
        id: String,
        token: String,
        displayName: String,
        username: String
    },
    google: {
        id: String,
        token: String,
        email: String,
        name: String
    },
    github: {
        id: String,
        token: String,
        email: String,
        name: String
    }
});

//methods ========================
//generation a hash
userSchema.methods.generateHash = function(password) {
    return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null);
};

// checking if a password is valid
userSchema.methods.validPassword = function(password) {
    return bcrypt.compareSync(password, this.local.password);
};

module.exports = userSchema;

Então vamos separar os objetos internos do userSchema para 1 molécula diferente para cada:

// molecule-local
module.exports = {
  email: String,
  password: String
}
// molecule-facebook
module.exports = {
  id: String,
  token: String,
  email: String,
  name: String
}
// molecule-twitter
module.exports = {
  id: String,
  token: String,
  displayName: String,
  username: String
}
// molecule-google
module.exports = {
  id: String,
  token: String,
  email: String,
  name: String
}
// molecule-github
module.exports = {
  id: String,
  token: String,
  email: String,
  name: String
}

Desse jeito deixamos nosso código MUITO menor:

const mongoose = require('mongoose');
const bcrypt = require('bcrypt-nodejs');

const generateHash = require('../quarks/quark-generateHash.js')
const isValidPassword = require('../quarks/quark-isValidPassword.js')

const local = require('./molecule-local')
const facebook = require('./molecule-facebook')
const github = require('./molecule-github')
const google = require('./molecule-google')
const twitter = require('./molecule-twitter')

const Structure = {
  local,
  facebook,
  twitter,
  google,
  github
}
const Molecule = mongoose.Schema(Structure);

Molecule.methods.generateHash = generateHash

Molecule.methods.validPassword = function(password) {
    return isValidPassword(password, this.local)
};

module.exports = Molecule;

E você também percebeu que modularizei os methods da Molecule para seus Quarks responsáveis:

// quark-generateHash
const bcrypt = require('bcrypt')

module.exports = (password) => {
  return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null)
};
// quark-isValidPassword
const bcrypt = require('bcrypt')

module.exports = (password, local) => {
  return bcrypt.compareSync(password, local.password)
};

Agora vamos atomizar as moléculas de cada estratégia, por exemplo o molecule-local.js:

const email = require('../atoms/email')
const password = require('../atoms/password')

module.exports = {
  email,
  password
}

Onde temos seus Átomos email e password:

// atoms/email.js
const AtomName = 'Email';

module.exports = {
  type: String
, validate: require('./../hadrons/'+AtomName.toLowerCase()+'ValidateMongoose')
, required: true
}
// atoms/password.js
const AtomName = 'Password';

module.exports = {
  type: String
, validate: require('./../hadrons/'+AtomName.toLowerCase()+'ValidateMongoose')
, required: true
}

Onde cada 1 tem seu próprio Hadron para validar com o Mongoose:

// hadrons/emailValidateMongoose
const QuarkName = 'Email';

module.exports = {
  validator: require('./../quarks/is'+QuarkName)
, message: require('./../quarks/is'+QuarkName+'Message')
};
// hadrons/passwordValidateMongoose
const QuarkName = 'Password';

module.exports = {
  validator: require('./../quarks/is'+QuarkName)
, message: require('./../quarks/is'+QuarkName+'Message')
};

Os quais chamam seus Quarks para validar em validator e de mensagem de erro em message.

Porém aqui nós não estamos usando o Quark isPassword para validar se a senha existe no banco, ela testa apenas o tamanho da String:

module.exports = (value) => {
  const isEmpty = require('./isEmpty')(value);
  const isString = require('./isString')(value);

  if(isEmpty) return false;
  if(!isString) return false;

  return (value.length > 6 && value.length < 20);
}

Pois o método que valida se a senha existe foi criado como função estática em:

Molecule.methods.validPassword = function(password) {
    return isValidPassword(password, this.local)
};

Que é utilizado em passport-local-login:

//all is well, return successful user.
if(!user.validPassword(password))

Releases

No releases published

Packages

No packages published