Make fixes based on review

This commit is contained in:
Tuan Dang
2024-07-09 11:51:21 +07:00
parent 96e485910c
commit 59ccabec69
25 changed files with 357 additions and 276 deletions
+1 -2
View File
@@ -816,8 +816,7 @@ export const registerRoutes = async (
});
const identityAccessTokenService = identityAccessTokenServiceFactory({
identityAccessTokenDAL,
identityOrgMembershipDAL,
permissionService
identityOrgMembershipDAL
});
const identityProjectService = identityProjectServiceFactory({
permissionService,
@@ -2,8 +2,6 @@ import { z } from "zod";
import { UNIVERSAL_AUTH } from "@app/lib/api-docs";
import { writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerIdentityAccessTokenRouter = async (server: FastifyZodProvider) => {
server.route({
@@ -63,37 +61,4 @@ export const registerIdentityAccessTokenRouter = async (server: FastifyZodProvid
};
}
});
server.route({
url: "/token/revoke-by-id",
method: "POST",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Revoke access token by the id of the token",
body: z.object({
tokenId: z.string().trim()
}),
response: {
200: z.object({
message: z.string()
})
}
},
handler: async (req) => {
await server.services.identityAccessToken.revokeAccessTokenById({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.body
});
return {
message: "Successfully revoked access token"
};
}
});
};
@@ -253,6 +253,15 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
}
});
// proposed
// update token by id: PATCH /token-auth/tokens/:tokenId
// revoke token by id: POST /token-auth/tokens/:tokenId/revoke
// current
// revoke token by id: POST /token/revoke-by-id
// token-auth/identities/:identityId/tokens
server.route({
method: "POST",
url: "/token-auth/identities/:identityId/tokens",
@@ -284,7 +293,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
},
handler: async (req) => {
const { identityTokenAuth, accessToken, identityAccessToken, identityMembershipOrg } =
await server.services.identityTokenAuth.createTokenTokenAuth({
await server.services.identityTokenAuth.createTokenAuthToken({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
@@ -342,7 +351,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
}
},
handler: async (req) => {
const { tokens, identityMembershipOrg } = await server.services.identityTokenAuth.getTokensTokenAuth({
const { tokens, identityMembershipOrg } = await server.services.identityTokenAuth.getTokenAuthTokens({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
@@ -368,7 +377,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
server.route({
method: "PATCH",
url: "/token-auth/identities/:identityId/tokens/:tokenId",
url: "/token-auth/tokens/:tokenId",
config: {
rateLimit: writeLimit
},
@@ -381,7 +390,6 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
}
],
params: z.object({
identityId: z.string(),
tokenId: z.string()
}),
body: z.object({
@@ -394,12 +402,11 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
}
},
handler: async (req) => {
const { token, identityMembershipOrg } = await server.services.identityTokenAuth.updateTokenTokenAuth({
const { token, identityMembershipOrg } = await server.services.identityTokenAuth.updateTokenAuthToken({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
identityId: req.params.identityId,
tokenId: req.params.tokenId,
...req.body
});
@@ -410,7 +417,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
event: {
type: EventType.UPDATE_TOKEN_IDENTITY_TOKEN_AUTH,
metadata: {
identityId: req.params.identityId,
identityId: token.identityId,
tokenId: token.id,
name: req.body.name
}
@@ -420,4 +427,42 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
return { token };
}
});
server.route({
method: "POST",
url: "/token-auth/tokens/:tokenId/revoke",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Revoke token for identity with Token Auth configured",
security: [
{
bearerAuth: []
}
],
params: z.object({
tokenId: z.string()
}),
response: {
200: z.object({
message: z.string()
})
}
},
handler: async (req) => {
await server.services.identityTokenAuth.revokeTokenAuthToken({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
tokenId: req.params.tokenId
});
return {
message: "Successfully revoked access token"
};
}
});
};
@@ -1,9 +1,6 @@
import { ForbiddenError } from "@casl/ability";
import jwt, { JwtPayload } from "jsonwebtoken";
import { TableName, TIdentityAccessTokens } from "@app/db/schemas";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
import { checkIPAgainstBlocklist, TIp } from "@app/lib/ip";
@@ -11,24 +8,18 @@ import { checkIPAgainstBlocklist, TIp } from "@app/lib/ip";
import { AuthTokenType } from "../auth/auth-type";
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
import { TIdentityAccessTokenDALFactory } from "./identity-access-token-dal";
import {
TIdentityAccessTokenJwtPayload,
TRenewAccessTokenDTO,
TRevokeAccessTokenByIdDTO
} from "./identity-access-token-types";
import { TIdentityAccessTokenJwtPayload, TRenewAccessTokenDTO } from "./identity-access-token-types";
type TIdentityAccessTokenServiceFactoryDep = {
identityAccessTokenDAL: TIdentityAccessTokenDALFactory;
identityOrgMembershipDAL: TIdentityOrgDALFactory;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
};
export type TIdentityAccessTokenServiceFactory = ReturnType<typeof identityAccessTokenServiceFactory>;
export const identityAccessTokenServiceFactory = ({
identityAccessTokenDAL,
identityOrgMembershipDAL,
permissionService
identityOrgMembershipDAL
}: TIdentityAccessTokenServiceFactoryDep) => {
const validateAccessTokenExp = async (identityAccessToken: TIdentityAccessTokens) => {
const {
@@ -147,43 +138,6 @@ export const identityAccessTokenServiceFactory = ({
return { revokedToken };
};
const revokeAccessTokenById = async ({
tokenId,
actorId,
actor,
actorAuthMethod,
actorOrgId
}: TRevokeAccessTokenByIdDTO) => {
const identityAccessToken = await identityAccessTokenDAL.findOne({
[`${TableName.IdentityAccessToken}.id` as "id"]: tokenId,
isAccessTokenRevoked: false
});
if (!identityAccessToken) throw new UnauthorizedError();
const identityOrgMembership = await identityOrgMembershipDAL.findOne({
identityId: identityAccessToken.identityId
});
if (!identityOrgMembership) {
throw new UnauthorizedError({ message: "Identity does not belong to any organization" });
}
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
identityOrgMembership.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
const revokedToken = await identityAccessTokenDAL.updateById(identityAccessToken.id, {
isAccessTokenRevoked: true
});
return { revokedToken };
};
const fnValidateIdentityAccessToken = async (token: TIdentityAccessTokenJwtPayload, ipAddress?: string) => {
const identityAccessToken = await identityAccessTokenDAL.findOne({
[`${TableName.IdentityAccessToken}.id` as "id"]: token.identityAccessTokenId,
@@ -221,5 +175,5 @@ export const identityAccessTokenServiceFactory = ({
return { ...identityAccessToken, orgId: identityOrgMembership.orgId };
};
return { renewAccessToken, revokeAccessToken, revokeAccessTokenById, fnValidateIdentityAccessToken };
return { renewAccessToken, revokeAccessToken, fnValidateIdentityAccessToken };
};
@@ -1,5 +1,3 @@
import { TProjectPermission } from "@app/lib/types";
export type TRenewAccessTokenDTO = {
accessToken: string;
};
@@ -10,7 +8,3 @@ export type TIdentityAccessTokenJwtPayload = {
identityAccessTokenId: string;
authTokenType: string;
};
export type TRevokeAccessTokenByIdDTO = {
tokenId: string;
} & Omit<TProjectPermission, "projectId">;
@@ -1,13 +1,13 @@
import { ForbiddenError } from "@casl/ability";
import jwt from "jsonwebtoken";
import { IdentityAuthMethod } from "@app/db/schemas";
import { IdentityAuthMethod, TableName } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { isAtLeastAsPrivileged } from "@app/lib/casl";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError, ForbiddenRequestError } from "@app/lib/errors";
import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
import { ActorType, AuthTokenType } from "../auth/auth-type";
@@ -18,12 +18,13 @@ import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identit
import { TIdentityTokenAuthDALFactory } from "./identity-token-auth-dal";
import {
TAttachTokenAuthDTO,
TCreateTokenTokenAuthDTO,
TCreateTokenAuthTokenDTO,
TGetTokenAuthDTO,
TGetTokensTokenAuthDTO,
TGetTokenAuthTokensDTO,
TRevokeTokenAuthDTO,
TRevokeTokenAuthTokenDTO,
TUpdateTokenAuthDTO,
TUpdateTokenTokenAuthDTO
TUpdateTokenAuthTokenDTO
} from "./identity-token-auth-types";
type TIdentityTokenAuthServiceFactoryDep = {
@@ -33,7 +34,10 @@ type TIdentityTokenAuthServiceFactoryDep = {
>;
identityDAL: Pick<TIdentityDALFactory, "updateById">;
identityOrgMembershipDAL: Pick<TIdentityOrgDALFactory, "findOne">;
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create" | "find" | "update">;
identityAccessTokenDAL: Pick<
TIdentityAccessTokenDALFactory,
"create" | "find" | "update" | "findById" | "findOne" | "updateById"
>;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
};
@@ -243,7 +247,7 @@ export const identityTokenAuthServiceFactory = ({
);
const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission);
if (!hasPriviledge)
throw new ForbiddenRequestError({
throw new UnauthorizedError({
message: "Failed to revoke Token Auth of identity with more privileged role"
});
@@ -255,14 +259,14 @@ export const identityTokenAuthServiceFactory = ({
return revokedIdentityTokenAuth;
};
const createTokenTokenAuth = async ({
const createTokenAuthToken = async ({
identityId,
actorId,
actor,
actorAuthMethod,
actorOrgId,
name
}: TCreateTokenTokenAuthDTO) => {
}: TCreateTokenAuthTokenDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.TOKEN_AUTH)
@@ -328,7 +332,7 @@ export const identityTokenAuthServiceFactory = ({
return { accessToken, identityTokenAuth, identityAccessToken, identityMembershipOrg };
};
const getTokensTokenAuth = async ({
const getTokenAuthTokens = async ({
identityId,
offset = 0,
limit = 20,
@@ -336,7 +340,7 @@ export const identityTokenAuthServiceFactory = ({
actor,
actorAuthMethod,
actorOrgId
}: TGetTokensTokenAuthDTO) => {
}: TGetTokenAuthTokensDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.TOKEN_AUTH)
@@ -350,20 +354,7 @@ export const identityTokenAuthServiceFactory = ({
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
identityMembershipOrg.identityId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission);
if (!hasPriviledge)
throw new ForbiddenRequestError({
message: "Failed to get tokens for identity with more privileged role"
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity);
const tokens = await identityAccessTokenDAL.find(
{
@@ -375,16 +366,17 @@ export const identityTokenAuthServiceFactory = ({
return { tokens, identityMembershipOrg };
};
const updateTokenTokenAuth = async ({
identityId,
const updateTokenAuthToken = async ({
tokenId,
name,
actorId,
actor,
actorAuthMethod,
actorOrgId
}: TUpdateTokenTokenAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
}: TUpdateTokenAuthTokenDTO) => {
const foundToken = await identityAccessTokenDAL.findById(tokenId);
if (!foundToken) throw new NotFoundError({ message: "Failed to find token" });
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId: foundToken.identityId });
if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.TOKEN_AUTH)
throw new BadRequestError({
@@ -414,7 +406,7 @@ export const identityTokenAuthServiceFactory = ({
const [token] = await identityAccessTokenDAL.update(
{
identityId,
identityId: foundToken.identityId,
id: tokenId
},
{
@@ -425,13 +417,54 @@ export const identityTokenAuthServiceFactory = ({
return { token, identityMembershipOrg };
};
const revokeTokenAuthToken = async ({
tokenId,
actorId,
actor,
actorAuthMethod,
actorOrgId
}: TRevokeTokenAuthTokenDTO) => {
const identityAccessToken = await identityAccessTokenDAL.findOne({
[`${TableName.IdentityAccessToken}.id` as "id"]: tokenId,
isAccessTokenRevoked: false
});
if (!identityAccessToken)
throw new NotFoundError({
message: "Failed to find token"
});
const identityOrgMembership = await identityOrgMembershipDAL.findOne({
identityId: identityAccessToken.identityId
});
if (!identityOrgMembership) {
throw new UnauthorizedError({ message: "Identity does not belong to any organization" });
}
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
identityOrgMembership.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
const revokedToken = await identityAccessTokenDAL.updateById(identityAccessToken.id, {
isAccessTokenRevoked: true
});
return { revokedToken };
};
return {
attachTokenAuth,
updateTokenAuth,
getTokenAuth,
revokeIdentityTokenAuth,
createTokenTokenAuth,
getTokensTokenAuth,
updateTokenTokenAuth
createTokenAuthToken,
getTokenAuthTokens,
updateTokenAuthToken,
revokeTokenAuthToken
};
};
@@ -24,19 +24,22 @@ export type TRevokeTokenAuthDTO = {
identityId: string;
} & Omit<TProjectPermission, "projectId">;
export type TCreateTokenTokenAuthDTO = {
export type TCreateTokenAuthTokenDTO = {
identityId: string;
name?: string;
} & Omit<TProjectPermission, "projectId">;
export type TGetTokensTokenAuthDTO = {
export type TGetTokenAuthTokensDTO = {
identityId: string;
offset: number;
limit: number;
} & Omit<TProjectPermission, "projectId">;
export type TUpdateTokenTokenAuthDTO = {
identityId: string;
export type TUpdateTokenAuthTokenDTO = {
tokenId: string;
name?: string;
} & Omit<TProjectPermission, "projectId">;
export type TRevokeTokenAuthTokenDTO = {
tokenId: string;
} & Omit<TProjectPermission, "projectId">;
@@ -5,7 +5,7 @@ import { TLicenseServiceFactory } from "@app/ee/services/license/license-service
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { isAtLeastAsPrivileged } from "@app/lib/casl";
import { BadRequestError, ForbiddenRequestError } from "@app/lib/errors";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { TOrgPermission } from "@app/lib/types";
import { TIdentityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
@@ -213,7 +213,7 @@ export const identityServiceFactory = ({
actorOrgId
}: TListProjectIdentitiesByIdentityIdDTO) => {
const identityOrgMembership = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityOrgMembership) throw new BadRequestError({ message: `Failed to find identity with id ${identityId}` });
if (!identityOrgMembership) throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
const { permission } = await permissionService.getOrgPermission(
actor,