'use strict';

const { UnauthorizedError, ForbiddenError } = require('@strapi/utils').errors;
const constants = require('../services/constants');
const { getService } = require('../utils');

const isReadScope = scope => scope.endsWith('find') || scope.endsWith('findOne');

const extractToken = ctx => {
  if (ctx.request && ctx.request.header && ctx.request.header.authorization) {
    const parts = ctx.request.header.authorization.split(/\s+/);

    if (parts[0].toLowerCase() !== 'bearer' || parts.length !== 2) {
      return null;
    }

    return parts[1];
  }

  return null;
};

/** @type {import('.').AuthenticateFunction} */
const authenticate = async ctx => {
  const apiTokenService = getService('api-token');
  const token = extractToken(ctx);

  if (!token) {
    return { authenticated: false };
  }

  const apiToken = await apiTokenService.getBy({
    accessKey: apiTokenService.hash(token),
  });

  if (!apiToken) {
    return { authenticated: false };
  }

  return { authenticated: true, credentials: apiToken };
};

/** @type {import('.').VerifyFunction} */
const verify = (auth, config) => {
  const { credentials: apiToken } = auth;

  if (!apiToken) {
    throw new UnauthorizedError();
  }

  if (apiToken.type === constants.API_TOKEN_TYPE.FULL_ACCESS) {
    return;
  }

  /**
   * If you don't have `full-access` you can only access `find` and `findOne`
   * scopes. If the route has no scope, then you can't get access to it.
   */

  const scopes = Array.isArray(config.scope) ? config.scope : [config.scope];
  if (config.scope && scopes.every(isReadScope)) {
    return;
  }

  throw new ForbiddenError();
};

/** @type {import('.').AuthStrategy} */
module.exports = {
  name: 'api-token',
  authenticate,
  verify,
};
