• Как реализовать авторизацию пользователя на стеке React Redux Node?

    maxfarseer
    @maxfarseer
    https://maxpfrontend.ru, обучаю реакту и компании
    Пригодится jwt. (гугл)

    В общих словах: вам нужно выдать юзеру токен. С этим токеном юзер будет выполнять запросы к вашим защищенным методам api (вы можете оставить какие-то методы "без токена").

    Node.js часть
    Пример роута на логин (сделано не лучшим образом, в плане коллбэков, но что нашлось под рукой...):

    auth.js
    ...
    const v4 = require('node-uuid').v4
    const jwt = require('jsonwebtoken')
    ...
    router.post('/signin', (req, res, next) => {
    
      // валидация, например... 
    
      if (errors) {
        return res.status(400).json({ errors })
      } else {
        // поиск юзера в базе, сравнение хэша и прочие необходимые операции
        ...
       // допустим все ок, нужно ответить токеном
      // генерируем необходимые опции и сам токен
    
            const payload = {
              _id: user._id,
              iss: 'http://localhost:3000',
              permissions: 'poll',
            }
            const options = {
              expiresIn: '7d',
              jwtid: v4(),
            }
            const secret = new Buffer(process.env.AUTH0_CLIENT_SECRET, 'base64')
            jwt.sign(payload, secret, options, (err, token) => {
              // отвечаем токеном, для будущих запросов с клиента
              return res.json({ data: token })
            })
         ...
    })
    
    module.exports = router;


    Пример защищенного роута (метода, который требует токен):
    ...
    const jwt = require('jsonwebtoken')
    const Poll = require('../models/poll')
    ...
    
    const requireToken = (req,res,next) => {
      const token = req.headers['x-api-key']
      const secret = new Buffer(process.env.AUTH0_CLIENT_SECRET, 'base64')
    
      jwt.verify(token, secret, (err, decoded) => {
        if (err) {
          return res.status(401).json({ error: err.message })
        }
        req.params.userId = decoded._id
        next()
      })
    }
    ...
    
    // обратите внимание на requireToken - функция вызывается при каждом обращении к роуту
    // то есть при каждом PUT запросе по адресу (например: PUT api/v1/products/1238914)
    // будет вызываться requireToken
    
    router.put('/:id', requireToken, (req, res, next) => {
      const { productId } = req.body
      const userId = req.params.userId
    
      Poll.findById(req.params.id)
        .populate({ path: 'products' })
        .exec((err, poll) => {
            // ... необходимые действия в базе, в процессе успешного зачтения голоса...
        }
    })


    Клиентское приложение:

    Типичный ActionCreator (в качестве библиотеки для запросов можно взять bluebird, isomorphic-fetch, да хоть нативный xhr)
    actions/LoginActions.js

    export function login(data) {
      return dispatch => {
        dispatch({ type: LOGIN_REQUEST })
    
        // request в данном случае - https://github.com/KyleAMathews/superagent-bluebird-promise
        return request.post(`${API_ROOT_V1}/auth/signin`)
          .send(data)
          .then(res => {
            if (!res.ok) {
              dispatch({ type: LOGIN_FAILURE })
            } else {
              dispatch({
                type: LOGIN_SUCCESS,
                data: res.body.data,
              })
              //сохранение токена в localStorage
              localStorage.setItem('cks_token', res.body.data)
            }
          }, err => {
            dispatch({ type: LOGIN_FAILURE })
          })
      }
    }


    Пример голосования с токеном (выше, в node.js части был метод апи, а это PUT запрос с клиента)

    export function vote(productId, voteId) {
      return dispatch => {
        dispatch({ type: POLL_VOTE_REQUEST })
    
        return request.put(`${API_ROOT_V1}/api/v1/vote/${voteId}`)
          .set('X-API-Key', localStorage.getItem('cks_token')) // установка ТОКЕНА в заголовок 'X-API-Key'
          .send({ productId })
          .then(res => {
            if (!res.ok) {
              dispatch({ type: POLL_VOTE_FAILURE })
            } else {
              dispatch({
                type: POLL_VOTE_SUCCESS,
                data: normalize(res.body.data, schema.poll),
              })
            }
          }, err => {
            dispatch({ type: POLL_VOTE_FAILURE })
          })
      }
    }


    Надеюсь код формы с кнопкой "голосовать" не трубется.
    ====

    Все это можно организовать удобнее, например, выставлять токен всем запросам автоматически (речь про клиентскую часть):
    import fetch from 'isomorphic-fetch'
    ...
    const defaultHeaders = {
      Accept: 'application/json',
      'Content-Type': 'application/json',
    }
    
    function buildHeaders() {
      const authToken = localStorage.getItem('TOKEN')
      return { ...defaultHeaders, Authorization: authToken }
    }
    ...
    export function httpPost(url, data) {
      const body = JSON.stringify(data)
    
      return fetch(url, {
        method: 'post',
        headers: buildHeaders(),
        body: body,
      })
      .then(... код оработки запроса ...)
    }


    ===

    Проверить наличие токена, можно с в роутере, с помощью хука на onEnter.

    root.js
    ...
    <Provider store={store}>
          <Router history={routerHistory}>
            {configRoutes(store)} // Роуты создаются функцией, чтобы можно было использовать внутри нее store.getState()
          </Router>
        </Provider>
    ...


    routes.js
    export default function configRoutes(store) {
      function _ensureAuthenticated(nextState, replace, callback) {
        const { dispatch } = store
        const { session } = store.getState()
        const { currentUser } = session
        let nextUrl
    
        if (!currentUser && localStorage.getItem('cks.token')) {
          dispatch(getCurrentAccount())
        } else if (!localStorage.getItem('cks.token')) {
          replace('/signin')
        }
        callback()
      }
    
      return (
        <Route path='/' component={App}>
          <Route path='/signin' component={SigninContainer} />
          <Route path='/signup' component={SignupContainer} />
    
          <Route path='/locked' component={AuthenticatedContainer} onEnter={_ensureAuthenticated}>
            <Route component={LockedArea}>
              <Route path='/locked/a' component={A} />
              <Route path='/locked/b/:id' component={B} />
              <Route path='/locked/c' component={C} />
            </Route>
          </Route>
        </Route>
      )


    Остается только создать AuthenticatedContainer в котором проверять: есть ли currentUser (в рамках этого примера) и если нет - возвращать false. А если есть - this.props.children (в которых будут дальнейшие роуты..)
    ===

    Итого:
    1) у вас есть API написанное на node.js.
    2) У этого API есть защищенные методы, которые проверяют наличия токена в http-запросе. Токен выдается при запросе на логин. Само собой, операция логина (то есть POST запрос на your-api/login) не требует токена.
    3) После удачного логина, вы получаете в ответе на свой запрос токен (это уже внутри клиентского кода, и если мы говорим о redux - то внутри асинхронного вызова в actions)
    4) Сохраняете токен (например, в localStorage)
    5) Во все необходимые http-запросы устанавливаете заголовок с токеном
    Ответ написан
    15 комментариев
  • Как правильно на БЭМ реализовать подобие глобального класса например для ссылок?

    isqua
    @isqua
    Научу HTML, CSS, JS, BEM и Git
    Создайте класс, например, `link`, и ставьте его всем ссылкам. Посмотрите, как это сделано на сайте БЭМ.
    Ответ написан
    Комментировать
  • Сортировка многомерного массива?

    thewind
    @thewind
    php программист, front / backend developer
    uasort($data, function($a, $b) {
     return $a['theme']==0 || $b['theme']==0 ? -1 : 0;
    });

    Попробуйте
    Ответ написан
    Комментировать
  • После редактирования файла появляется файл который мешает при работе с гит?

    Похоже на файлы под undo.
    В этом случае их можно выключить (set noundofile), но поступать так крайне не рекомендую. Лучше изменить директорию под их хранение с "относительно текущей" на что-нибудь абсолютное вне проекта (set undodir=). Причем нужно иметь ввиду, что эта директория уже должна быть как-то создана и быть доступна для Vim, поскольку сам он её не создает.
    Ответ написан
    2 комментария
  • Как создать JSON поиск?

    HoHsi
    @HoHsi
    function seachArticle( search ){
      var obj = JSON.parse(/* Json */);
      var find;
    
      for (key in obj) {
        if(key === search) {
          find = obj[ key ];
          break;
        }
      }
    
      return find;
    }
    Ответ написан
    Комментировать
  • Как подсчитать голоса?

    MaxDukov
    @MaxDukov
    впишусь в проект как SRE/DevOps.
    ну так посчитайте среднее арифметическое - и округлите.
    Ответ написан
    Комментировать
  • Счетчик на нативном JS?

    Taraflex
    @Taraflex
    Ищу работу. Контакты в профиле.
    https://jsfiddle.net/QW01_01/w45v1hdc/1/
    var makeCounter = function(counterName){
    	var c = 0;
    
        Object.defineProperty(window, counterName, {
          enumerable: false,
          configurable: false,
          get:function(){
              return c++;
          }
        });
    
    }
    
    makeCounter('counter');
    
    counter;
    counter;
    counter;
    
    console.log(counter);
    Ответ написан
    8 комментариев
  • Порядковый номер обхода foreach?

    27cm
    @27cm
    TODO: Написать статус
    Это и есть "стандартный способ".

    Если массив индексированный, причём с индексами 0, 1, 2, 3..., то можно проще:
    foreach ($arr as $i => $val) {
        // тело цикла
    }


    Если массив ассоциативный, но вам не нужны его ключи:
    foreach (array_values($arr) as $i => $val) {
        // тело цикла
    }
    Ответ написан
    Комментировать
  • Как убрать повторяющиеся слэши в .htaccess?

    @ShamblerR
    Дааю сразу все поскольку в свое время задолбало для каждого проекта делать все заново, по этому сейчас просто вставляю кусок и все.
    ############################################################################
    #### Cтандартный .htaccess для проектов студии Клондайк, версия 2.3     ####
    ############################################################################
    RewriteEngine On
       #  Директива включает редиректы.
    RewriteBase / 
       # Без директивы (.*) = /$1 будет /var/wwww/site/web/$1  с директивой  = /$1
    Options +FollowSymLinks
       # Разрешает переход по символическим ссылкам.
    
    ############################################################################
    #### Перенаправляем протокол https на http                              ####
    ############################################################################
    RewriteCond %{HTTPS} on
       # Проверяем наличие https в URL.
    RewriteRule ^.*$ http://%{SERVER_NAME}%{REQUEST_URI}
       # Перенаправляем протокол на http.
    
    ############################################################################
    #### Выбор основного зеркала (или с www или без www)                    ####
    ############################################################################
       # 1. Редирект с www на без www. (раскоментировать директивы пункта 1)
    #RewriteCond %{HTTP_HOST} ^www\.(.*) [NC]
       # Проверяем, содержит ли домен www (в начале URL).
    #RewriteRule ^(.*)$ http://%1/$1 [R=301,L]
       # Перенаправляем URL на домен без www.
    ####
       # 2. Редирект без www на www. (раскоментировать директивы пункта 2)
    #RewriteCond %{HTTP_HOST} !^www\.(.*) [NC]
       # Проверяем, не содержит ли домен www (в начале URL).
    #RewriteRule ^(.*)$ http://www.%{HTTP_HOST}/$1 [R=301,L]
       # Перенаправляем URL на домен c www.
    
    ############################################################################
    #### Убираем повторяющиеся слеши (/) в URL                              ####
    ############################################################################
    RewriteCond %{REQUEST_URI} ^(.*)/{2,}(.*)$
       # Проверяем, повторяется ли слеш (//) более двух раз.
    RewriteRule . %1/%2 [R=301,L]
       # Исключаем все лишние слеши.
    
    ############################################################################
    #### Убираем слеши в конце URL для статических файлов (содержит точку)  ####
    ############################################################################
    RewriteCond %{REQUEST_URI} \..+$  
       # Если файл содержит точку.
    RewriteCond %{REQUEST_FILENAME} !-d   
       # И это не директория.
    RewriteCond %{REQUEST_FILENAME} -f
       # Является файлом.
    RewriteCond %{REQUEST_URI} ^(.+)/$      
       # И в конце URL есть слеш.
    RewriteRule ^(.+)/$ /$1 [R=301,L]     
       # Исключить слеш.
    
    ############################################################################
    #### Добавляем слеш(/), если его нет, и это не файл.                    ####
    ############################################################################
    RewriteCond %{REQUEST_URI} !(.*)/$
       # Если слеша в конце нет.
    RewriteCond %{REQUEST_FILENAME} !-f
       # Не является файлом.
    RewriteCond %{REQUEST_URI} !\..+$
       # В URL нет точки (файл).
    RewriteRule ^(.*)$ $1/ [L,R=301]
       # Добавляем слеш в конце.
    
    ############################################################################
    #### Убираем index.php, если он есть в конце URL                        ####
    ############################################################################
    RewriteCond %{REQUEST_METHOD} =GET
       # Выявляем GET запрос в URL (не POST).
    RewriteCond %{REQUEST_URI} ^(.*)/index\.php$
       # URL cодержит index.php в конце.
    RewriteRule ^(.*)$ %1/ [R=301,L]
       # Удалить index.php из URL.
    
    ############################################################################
    #### Конец общей части, далее следует собственные директивы .htaccess   ####
    ############################################################################
    Ответ написан
    5 комментариев
  • В чем разница между --save-dev и --save?

    k12th
    @k12th
    console.log(`You're pulling my leg, right?`);
    devDependencies — пакеты, которые нужны для разработки. Всякие галпы-гранты и плагины к ним обычно подпадают в эту категорию.
    dependencies — пакеты, от которых ваш пакет зависит непосредственно: как правило — библиотеки.
    Ставятся они все в node_modules.
    Тут подробнее: https://toster.ru/answer?answer_id=559717#comments...

    Если кто-то делает npm install вашему пакету, то npm подсосет те пакеты, которые указаны у него в dependencies, но не в devDependencies.
    Если сделать npm install внутри папки, в которой есть package.json, то установятся и те, и другие.
    Ответ написан
    3 комментария
  • Есть ли русскоязычные аналоги Code School?

    Assargin
    @Assargin
    Перед ответом смотрю наличие ✔ в ваших вопросах
    Нисколько не умаляя важности наличия таких курсов на русском для многих людей, оффтопно заявлю, что я с полгода назад, когда начинал обучаться на codeschool и coursera, сразу понимал, на что шел. Ожидание переводов и локализаций счёл бессмысленным, да и какой он еще будет, перевод-то качеством. А проходя курсы напрямую на забугорных проектах, заодно и английский подтянется (кроме разговорного, конечно же). Первыми моими курсами на английском были курсы от Mongo: сначала привыкал долго, до этого никогда не имел опыта в восприятии разговорной речи обычных американцев (субтитры очень помогали), зато потом втянулся.
    Ответ написан
    1 комментарий