🌊

ArangoDBのFoxxで認証システムを作る

2021/10/05に公開

Authentication

https://www.arangodb.com/docs/stable/foxx-guides-auth.html
を実際にやってみたので記載します

フォルダ構成

フォルダー パスの一覧
ボリューム シリアル番号は 347F-7D7D です
E:.
│  index.js
│  manifest.json
│
├─scripts
│      setup.js
│
└─util
        auth.js
        sessions.js

注意するべき点

module.context.collection('コレクション名')というふうにコレクション名を指定してますが、このように指定することでfoxapp名_コレクション名となる。
foxapp名_コレクション名

auth.js

'use strict';

// auth用モジュール
const createAuth = require('@arangodb/foxx/auth');

// authの設定
const auth = createAuth();

// 外部利用のためにエクスポート
module.exports = auth;

sessions.js

'use strict';

// セッション利用用モジュールロード
const sessionsMiddleware = require('@arangodb/foxx/sessions');

// セッションに関しての設定
const sessions = sessionsMiddleware({
  storage: module.context.collection('sessions'),   // ストレージはsessionsドキュメント
  transport: 'cookie'         // cookie利用
});

// 外部利用のためにエクスポート
module.exports = sessions;

setup.js

'use strict';

// ArangoDB使用するためのライブラリ
const db = require('@arangodb').db;

// usersコレクションがなければ作成する
const usersCN = module.context.collectionName('users');
if (!db._collection(usersCN)) {
  db._createDocumentCollection(usersCN);
}

// sessionsコレクションがなければ作成する
const sessions = module.context.collectionName('sessions');
if (!db._collection(sessions)) {
  db._createDocumentCollection(sessions);
}

// インデックス作成(usernameをユニークキーに)
module.context.collection('users').ensureIndex({
  type: 'hash',
  unique: true,
  fields: ['username']
});

// ユーザーを作成する(authを利用してパスワード生成)
const auth = require('../util/auth');
const users = module.context.collection('users');
if (!users.firstExample({ username: 'admin' })) {
  users.save({
    username: 'admin',
    password: auth.create('hunter2')
  });
}

manifest.json

{
  "engines": {
    "arangodb": "^3.0.0"
  },
  "main": "index.js",
  "scripts": {
    "setup": "scripts/setup.js"
  }
}

index.js

'use strict';

// 認証用
const auth = require('./util/auth');

// セッション用
const sessions = require('./util/sessions');
module.context.use(sessions);

// ユーザーコレクション
const users = module.context.collection('users');

// joi 値チェック用ライブラリ
const joi = require('joi');

// foxx用ルータ
const createRouter = require('@arangodb/foxx/router');
const router = createRouter();
module.context.use(router);

// /login の処理
router.post('/login', function(req, res) {
  // データ取得(username = req.body.username)
  const user = users.firstExample({
    username: req.body.username
  });

  // ユーザーおよびパスワードを確認する
  const valid = auth.verify(
    user ? user.password : {},    // ユーザーデータが取ってこれなかったときは空っぽにする
    req.body.password             // 送られてきたパスワード
  );

  // 確認できなかった?
  if (!valid) {
    // unauthorizedを返す
    res.throw('unauthorized');
  }

  // ユーザー名は変更される場合があるので_keyをセッションに保持する
  req.session.uid = user._key;

  // ストレージに保存
  req.sessionStorage.save(req.session);

  // ユーザー名を返す
  res.send({ username: user.username });
})
.body(
  // 入力チェック
  joi
    .object({
      username: joi.string().required(),
      password: joi.string().required()
    })
    .required()
);

// /me の処理
router.get('/me', function(req, res) {
  try {
    // セッションからユーザーを取得
    const user = users.document(req.session.uid);

    // ユーザー名を返す
    res.send({ username: user.username });
  } catch (e) {
    // not foundを返す
    res.throw('not found');
  }
});

// /logout の処理
router.post('/logout', function(req, res) {
  // セッションにデータが有る?
  if (req.session.uid) {
    // セッションのクリア
    req.session.uid = null;

    // ストレージのクリア
    req.sessionStorage.save(req.session);
  }
  // レスポンスをno contentとする(204)
  res.status('no content');
});

router.get('/login', function(req, res) {
  res.send(`<html>
<head>
<meta charset="utf-8">
<script src="https://unpkg.com/vue@next"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
<div id="basic-event">
  <label for="username">Enter your name: </label>
  <input type="text" v-model="username" id="username" required>
  <label for="password">Enter your password: </label>
  <input type="password" v-model="password" id="password" required>
  <button @click="login">ログイン</button>
  <button @click="me">ME</button>
</div>
</body>
<script>
Vue.createApp({
  data() {
    return {
      username: 'admin',
      password: 'hunter2',
    }
  },
  methods: {
    login(e) {
      axios.post('./login', {
        username: this.username,
        password: this.password,
      })
      .then(res => console.log(res.data))
    },
    me(e) {
      axios.get('./me')
      .then(res => console.log(res.data))
    },
  }
}).mount('#basic-event')
</script>
</html>
`
  );
})
.response(['text/html'], 'A generic greeting.')
.summary('Generic greeting')
.description('Prints a generic greeting.');

データに関して

authfoxx_sessionsコレクション

データ

_id:authfoxx_sessions/f0f87fb4404b8b72ce56d13c7717987e8a6494c7511a8c04b83bac16750a708f
_rev:_dC7eayy---
_key:f0f87fb4404b8b72ce56d13c7717987e8a6494c7511a8c04b83bac16750a708f
{
  "uid": "150448",
  "created": 1633419288966,
  "expires": 1633422893517,
  "data": null
}

このような感じで、_keyの値をcookieのsidに保存している
sid
uidにはauthfoxx_usersコレクションの各データの_key値が入っている(これでauthfoxx_sessionsauthfoxx_usersが出来ている)

authfoxx_usersコレクション

データ

_id:authfoxx_users/150448
_rev:_dC3cqgW---
_key:150448
{
  "username": "admin",
  "password": {
    "method": "sha256",
    "salt": "c7?Vp&ZY<z7z?Ul{",
    "hash": "c8380aca258d174698e099960c0d6227f7003a55f67ca95173b1c5433507c157"
  }
}

Discussion