// https://iwiki.woa.com/pages/viewpage.action?pageId=4007728669
// node version >= v12.22
// npm i jose@4.14.4
import { UserType } from '@gadmin2/nest-common';
import {
  CanActivate,
  ExecutionContext,
  Injectable,
  UnauthorizedException,
} from '@nestjs/common';
const crypto = require('crypto'); //改成import，运行时会报错
const jose = require('jose'); //改成import，运行时会报错

async function decodeAuthorizationHeader(authorizationHeader, key) {
  const dec = await jose.compactDecrypt(authorizationHeader, key);
  const payload = JSON.parse(dec.plaintext as any);

  const exp = new Date(payload.Expiration);
  // 检验 token 是否已经过期，增加5分钟缓冲，避免服务器时间差异
  if (new Date(new Date().getTime() - 3 * 60 * 1000) > exp) {
    // demo 为了正常运行，此处异常注释了，实际环境需要开启验证
    throw new Error('expired');
  }
  return payload;
}

function checkSignature(key, timestampSeconds, signature, extHeaders) {
  if (!timestampSeconds || isNaN(timestampSeconds)) {
    return false;
  }
  if (
    !(Math.abs(parseInt(timestampSeconds, 10) * 1000 - Date.now()) <= 180000)
  ) {
    // demo 为了正常运行，此处异常注释了，实际环境需要开启验证
    return false;
  }

  const hash = crypto.createHash('sha256');
  hash.update(timestampSeconds + key + extHeaders.join(',') + timestampSeconds);
  return signature.toLowerCase() === hash.digest('hex').toLowerCase();
}

async function getIdentity(headers, key) {
  const { staffid = '', staffname = '' } = headers;
  const extHeaders = [
    headers['x-rio-seq'],
    staffid,
    staffname,
    headers['x-ext-data'] || '',
  ];
  if (!checkSignature(key, headers.timestamp, headers.signature, extHeaders)) {
    throw new Error('check smartgate signature failed');
  }
  const taiIdentity = headers['x-tai-identity'];
  if (taiIdentity) {
    const payload = await decodeAuthorizationHeader(taiIdentity, key);
    return { staffid: payload.StaffId, staffname: payload.LoginName };
  }
  return { staffid: parseInt(staffid, 10) || 0, staffname };
}

@Injectable()
export class WoaAuthGuard implements CanActivate {
  constructor(private readonly token: string) {}

  canActivate(context: ExecutionContext): boolean | Promise<boolean> {
    const request = context.switchToHttp().getRequest();
    return getIdentity(request.headers, Buffer.from(this.token))
      .then((userInfo) => {
        const user: UserType = {
          userid: userInfo.staffid,
          username: userInfo.staffname,
        };
        request.user = user;
        return true;
      })
      .catch((error) => {
        throw new UnauthorizedException(error);
      });
  }
}
