forattini-dev/s3db.js

View on GitHub
examples/6-jwt-tokens.js

Summary

Maintainability
A
1 hr
Test Coverage
const { ENV, S3db } = require("./concerns");

const jwt = require("jsonwebtoken");
const { nanoid } = require("nanoid");
const Fakerator = require("fakerator");
const sha256 = require("crypto-js/sha256");
const { take, shuffle } = require("lodash");

const fake = Fakerator();

const userFactory = () => {
  const scopes = ["admin", "guest", "users:read", "tokens:read"];
  const email = fake.internet.email();

  return {
    id: email,
    email,
    name: fake.names.name(),
    password: nanoid(),
    scopes: take(shuffle(scopes), fake.random.number(scopes.length)),
  };
};

const Token = {
  createToken: async (s3db, email, password) => {
    const user = await s3db.resource("users").get(email);

    if (user.password !== password) {
      console.log({ user, email, password });
      throw new Error("invalid-user");
    }

    const data = {
      name: user.name,
      email: user.email,
      scopes: user.scopes,
      email_verified: true,
    };

    const token = jwt.sign(data, ENV.PASSPRHASE, {
      expiresIn: "2s",
      subject: "test",
      issuer: "s3db.js",
      audience: "default",
    });

    const decoded = jwt.decode(token, ENV.PASSPRHASE);

    await s3db.resource("tokens").insert({
      id: sha256(token).toString(),
      ...decoded,
    });

    return token;
  },

  validateToken: async (client, token) => {
    const tokenId = sha256(token).toString();

    try {
      const decoded = jwt.decode(token, ENV.PASSPRHASE);
      const savedToken = await client.resource("tokens").get(tokenId);

      return [null, { decoded, savedToken }];
    } catch (error) {
      return [error];
    }
  },
};

async function main() {
  const s3db = new S3db({
    uri: ENV.CONNECTION_STRING + Date.now(),
    passphrase: ENV.PASSPRHASE,
    parallelism: ENV.PARALLELISM,
  });

  await s3db.connect();

  await s3db.createResource({
    name: "users",
    attributes: {
      name: "string",
      email: "string",
      password: "secret",
      scopes: "array|items:string",
    },
  });

  await s3db.createResource({
    name: "tokens",
    attributes: {
      iss: ["string", "url"],
      sub: "string",
      aud: "string",
      exp: "number",
      email: "email",
      name: "string",
      email_verified: "boolean",
      scopes: "array|items:string",
    },
  });

  const users = new Array(5).fill(0).map(userFactory);
  await s3db.resource("users").insertMany(users);

  let tokens = [];
  process.stdout.write("Created tokens: ");
  for (const user of users) {
    const token = await Token.createToken(s3db, user.email, user.password);
    tokens.push(token);
    process.stdout.write(".");
  }

  process.stdout.write("\nValidated tokens: ");
  for (const token of tokens) {
    const [error, data] = await Token.validateToken(s3db, token);
    if (!error) {
      process.stdout.write(".");
    }
  }
}

main();