feat: create token exchange service to hitphone integration and microsoft token validation

feat-hitphone-integration
Henrriky 2024-05-08 12:29:19 -03:00
parent 975f7504aa
commit 9fd234667d
2 changed files with 174 additions and 0 deletions

View File

@ -0,0 +1,68 @@
import jwt, { JwtPayload } from "jsonwebtoken";
import JwksRsa from "jwks-rsa";
interface Token {
aud: string;
iss: string;
sub: string;
preferred_username: string;
}
const getTokenKid = (token: string) => {
const headers = token.split(".")[0];
if (!headers) {
throw new Error("No headers");
}
const buffer = Buffer.from(headers, "base64");
const text = buffer.toString("ascii");
const kid = JSON.parse(text).kid;
if (!kid) {
throw new Error("No kid");
}
if (typeof kid !== "string") {
throw new Error("Invalid kid");
}
return kid;
}
const getTokenPublicKey = async (token: string): Promise<string> => {
const kid = getTokenKid(token);
const publicKey = (
await JwksRsa({
cache: true,
rateLimit: true,
jwksUri: "https://login.microsoftonline.com/common/discovery/keys",
}).getSigningKey(kid)
).getPublicKey();
return publicKey;
};
export const verifyTeamsToken = async (token: string): Promise<{ payload: Token & JwtPayload }> => {
let publicKey;
try {
publicKey = await getTokenPublicKey(token);
} catch (error) {
throw new Error(
`Error getting publicKey to verify token\nReason: ${error}`
);
}
const decoded = jwt.verify(
token,
publicKey,
{
algorithms: ["RS256"],
}
) as Token;
return { payload: decoded }
};

View File

@ -0,0 +1,106 @@
import { verify, Algorithm, JwtPayload } from "jsonwebtoken";
import AppError from "../../errors/AppError";
import authConfig from "../../config/auth";
import {
createAccessToken,
createRefreshToken
} from "../../helpers/CreateTokens";
import User from "../../models/User";
import { SerializeUser, SerializedUser } from "../../helpers/SerializeUser";
import { verifyTeamsToken } from "../../libs/teamsTokenValidation";
import { clientExists } from "../External/HitphoneServices/ClientExists";
type TokenVerifierResponse = {
name: string;
email: string;
sub: string;
}
export const verifyTokenFromWebService = async (token: string): Promise<TokenVerifierResponse> => {
const userPayload = verify(
token,
authConfig.hitphone.jwtPublicKey,
{
algorithms: [authConfig.hitphone.jwtAlgorithm as Algorithm],
audience: authConfig.hitphone.jwtAudience,
issuer: authConfig.hitphone.jwtIssuer,
}
) as JwtPayload;
const requiredFields = ["email"];
for (const key of requiredFields) {
if (!userPayload[key]) {
throw new AppError(`ERR_TOKEN_${key.toUpperCase()}_INVALID`, 401)
}
}
return { name: userPayload.email, email: userPayload.email, sub: userPayload.sub! }
}
export const verifyTokenFromTeamsService = async (token: string): Promise<TokenVerifierResponse> => {
const { payload: userPayload } = await verifyTeamsToken(token)
if (userPayload.aud !== authConfig.hitphone.teams.CLIENT_ID) {
throw new AppError("ERR_TOKEN_AUD_INVALID", 401);
}
const requiredFields = ["aud", "tid", "oid", "preferred_username"];
for (const key of requiredFields) {
if (!userPayload[key]) {
throw new AppError(`ERR_TOKEN_${key.toUpperCase()}_INVALID`, 401)
}
}
const exists = await clientExists(userPayload.tid);
if (!exists) {
throw new AppError("ERR_CLIENT_NOT_FOUND", 401);
}
return { name: userPayload.preferred_username, email: userPayload.preferred_username, sub: userPayload.sub }
}
type TokenExchangeServiceRequest = {
token: string;
tokenVerifier: (token: string) => Promise<TokenVerifierResponse>
}
export type TokenExchangeServiceResponse = {
token: string;
refreshToken: string;
serializedUser: SerializedUser
}
export const TokenExchangeService = async (
{
token,
tokenVerifier
}: TokenExchangeServiceRequest): Promise<TokenExchangeServiceResponse> => {
try {
const { email } = await tokenVerifier(token);
const user = await User.findOne({
where: { email },
include: ["queues"]
});
if (!user) {
throw new AppError("ERR_INVALID_CREDENTIALS", 401);
}
const generatedToken = createAccessToken(user);
const generatedRefreshToken = createRefreshToken(user);
const generatedSerializedUser = SerializeUser(user)
return {
token: generatedToken,
refreshToken: generatedRefreshToken,
serializedUser: generatedSerializedUser,
}
} catch (err) {
console.error(err)
throw new AppError("ERR_INVALID_TOKEN", 401);
}
};