Update audit log service actor v3 filter, status toggle permissions, add JWT service token secret, for ST V3

This commit is contained in:
Tuan Dang
2023-09-26 12:03:00 +01:00
parent 4683dc7869
commit aa1e0b0f28
10 changed files with 85 additions and 27 deletions
+1
View File
@@ -26,6 +26,7 @@ export const getJwtSignupLifetime = async () => (await client.getSecret("JWT_SIG
export const getJwtProviderAuthSecret = async () => (await client.getSecret("JWT_PROVIDER_AUTH_SECRET")).secretValue; export const getJwtProviderAuthSecret = async () => (await client.getSecret("JWT_PROVIDER_AUTH_SECRET")).secretValue;
export const getJwtProviderAuthLifetime = async () => (await client.getSecret("JWT_PROVIDER_AUTH_LIFETIME")).secretValue || "15m"; export const getJwtProviderAuthLifetime = async () => (await client.getSecret("JWT_PROVIDER_AUTH_LIFETIME")).secretValue || "15m";
export const getJwtSignupSecret = async () => (await client.getSecret("JWT_SIGNUP_SECRET")).secretValue; export const getJwtSignupSecret = async () => (await client.getSecret("JWT_SIGNUP_SECRET")).secretValue;
export const getJwtServiceTokenSecret = async () => (await client.getSecret("JWT_SERVICE_TOKEN_SECRET")).secretValue;
export const getMongoURL = async () => (await client.getSecret("MONGO_URL")).secretValue; export const getMongoURL = async () => (await client.getSecret("MONGO_URL")).secretValue;
export const getNodeEnv = async () => (await client.getSecret("NODE_ENV")).secretValue || "production"; export const getNodeEnv = async () => (await client.getSecret("NODE_ENV")).secretValue || "production";
export const getVerboseErrorOutput = async () => (await client.getSecret("VERBOSE_ERROR_OUTPUT")).secretValue === "true" && true; export const getVerboseErrorOutput = async () => (await client.getSecret("VERBOSE_ERROR_OUTPUT")).secretValue === "true" && true;
@@ -22,6 +22,7 @@ import {
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { BadRequestError, ResourceNotFoundError } from "../../utils/errors"; import { BadRequestError, ResourceNotFoundError } from "../../utils/errors";
import { EEAuditLogService } from "../../ee/services"; import { EEAuditLogService } from "../../ee/services";
import { getJwtServiceTokenSecret } from "../../config";
/** /**
* Create service token data * Create service token data
@@ -82,7 +83,7 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
_id: serviceTokenData._id.toString() _id: serviceTokenData._id.toString()
}, },
expiresIn, expiresIn,
secret: "hello" // TODO: replace with real secret secret: await getJwtServiceTokenSecret()
}); });
await EEAuditLogService.createAuditLog( await EEAuditLogService.createAuditLog(
@@ -5,6 +5,7 @@ import {
Membership, Membership,
Secret, Secret,
ServiceTokenData, ServiceTokenData,
ServiceTokenDataV3,
TFolderSchema, TFolderSchema,
User, User,
Workspace Workspace
@@ -20,6 +21,7 @@ import {
SecretSnapshot, SecretSnapshot,
SecretVersion, SecretVersion,
ServiceActor, ServiceActor,
ServiceActorV3,
TFolderRootVersionSchema, TFolderRootVersionSchema,
TrustedIP, TrustedIP,
UserActor UserActor
@@ -668,13 +670,13 @@ export const getWorkspaceAuditLogs = async (req: Request, res: Response) => {
: {}), : {}),
...(actor ...(actor
? { ? {
"actor.type": actor.split("-", 2)[0], "actor.type": actor.substring(0, actor.lastIndexOf("-")),
...(actor.split("-", 2)[0] === ActorType.USER ...(actor.split("-", 2)[0] === ActorType.USER
? { ? {
"actor.metadata.userId": actor.split("-", 2)[1] "actor.metadata.userId": actor.substring(actor.lastIndexOf("-") + 1)
} }
: { : {
"actor.metadata.serviceId": actor.split("-", 2)[1] "actor.metadata.serviceId": actor.substring(actor.lastIndexOf("-") + 1)
}) })
} }
: {}), : {}),
@@ -743,8 +745,26 @@ export const getWorkspaceAuditLogActorFilterOpts = async (req: Request, res: Res
} }
})); }));
const serviceV3Actors: ServiceActorV3[] = (
await ServiceTokenDataV3.find({
workspace: new Types.ObjectId(workspaceId)
})
).map((serviceTokenData) => ({
type: ActorType.SERVICE_V3,
metadata: {
serviceId: serviceTokenData._id.toString(),
name: serviceTokenData.name
}
}));
const actors = [
...userActors,
...serviceActors,
...serviceV3Actors
];
return res.status(200).send({ return res.status(200).send({
actors: [...userActors, ...serviceActors] actors
}); });
}; };
+16 -8
View File
@@ -24,6 +24,7 @@ import {
getJwtProviderAuthSecret, getJwtProviderAuthSecret,
getJwtRefreshLifetime, getJwtRefreshLifetime,
getJwtRefreshSecret, getJwtRefreshSecret,
getJwtServiceTokenSecret
} from "../config"; } from "../config";
import { import {
AuthMode AuthMode
@@ -87,7 +88,7 @@ export const validateAuthMode = ({
break; break;
case "proj_token": case "proj_token":
authMode = AuthMode.SERVICE_TOKEN_V3; authMode = AuthMode.SERVICE_TOKEN_V3;
authTokenValue = parts.slice(1).join('.'); authTokenValue = parts.slice(1).join(".");
break; break;
default: default:
authMode = AuthMode.JWT; authMode = AuthMode.JWT;
@@ -239,21 +240,28 @@ export const getAuthSTDPayload = async ({
authTokenValue: string; authTokenValue: string;
}): Promise<ServiceTokenV3AuthData> => { }): Promise<ServiceTokenV3AuthData> => {
const decodedToken = <jwt.UserIDJwtPayload>( const decodedToken = <jwt.UserIDJwtPayload>(
jwt.verify(authTokenValue, "hello") // TODO: replace with real secret jwt.verify(authTokenValue, await getJwtServiceTokenSecret())
); );
const serviceTokenData = await ServiceTokenDataV3.findOne({ const serviceTokenData = await ServiceTokenDataV3.findOneAndUpdate(
_id: new Types.ObjectId(decodedToken._id), {
isActive: true _id: new Types.ObjectId(decodedToken._id),
}); isActive: true
},
{
lastUsed: new Date()
},
{
new: true
}
);
if (!serviceTokenData) { if (!serviceTokenData) {
throw UnauthorizedRequestError({ throw UnauthorizedRequestError({
message: "Failed to authenticate" // standardize auth error messages message: "Failed to authenticate"
}); });
} else if (serviceTokenData?.expiresAt && new Date(serviceTokenData.expiresAt) < new Date()) { } else if (serviceTokenData?.expiresAt && new Date(serviceTokenData.expiresAt) < new Date()) {
// case: service token expired // case: service token expired
// TODO: test expired token
await ServiceTokenDataV3.findByIdAndUpdate( await ServiceTokenDataV3.findByIdAndUpdate(
serviceTokenData._id, serviceTokenData._id,
{ {
+2 -1
View File
@@ -1,6 +1,7 @@
export enum ActorType { export enum ActorType {
USER = "user", USER = "user",
SERVICE = "service" SERVICE = "service",
SERVICE_V3 = "service-v3"
} }
export enum UserAgentType { export enum UserAgentType {
+7 -1
View File
@@ -35,9 +35,15 @@ export interface ServiceActor {
metadata: ServiceActorMetadata; metadata: ServiceActorMetadata;
} }
export interface ServiceActorV3 {
type: ActorType.SERVICE_V3;
metadata: ServiceActorMetadata;
}
export type Actor = export type Actor =
| UserActor | UserActor
| ServiceActor; | ServiceActor
| ServiceActorV3;
interface GetSecretsEvent { interface GetSecretsEvent {
type: EventType.GET_SECRETS; type: EventType.GET_SECRETS;
@@ -50,6 +50,12 @@ export const LogsFilter = ({
{actor.metadata.name} {actor.metadata.name}
</SelectItem> </SelectItem>
); );
case ActorType.SERVICE_V3:
return (
<SelectItem value={`${actor.type}-${actor.metadata.serviceId}`} key={`service-actor-v3-filter-${actor.metadata.serviceId}`}>
{actor.metadata.name}
</SelectItem>
);
default: default:
return ( return (
<SelectItem value="actor-none" key="actor-none"> <SelectItem value="actor-none" key="actor-none">
@@ -29,6 +29,13 @@ export const LogsTableRow = ({
<p>Service token</p> <p>Service token</p>
</Td> </Td>
); );
case ActorType.SERVICE_V3:
return (
<Td>
<p>{`${actor.metadata.name}`}</p>
<p>Service token V3</p>
</Td>
);
default: default:
return ( return (
<Td /> <Td />
@@ -50,7 +50,7 @@ export const ServiceTokenV3Section = withProjectPermission(
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"> <div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="flex justify-between mb-8"> <div className="flex justify-between mb-8">
<p className="text-xl font-semibold text-mineshaft-100"> <p className="text-xl font-semibold text-mineshaft-100">
(New) Service Tokens Service Tokens V3 (Beta)
</p> </p>
<ProjectPermissionCan <ProjectPermissionCan
I={ProjectPermissionActions.Create} I={ProjectPermissionActions.Create}
@@ -114,16 +114,24 @@ export const ServiceTokenV3Table = ({
<Tr className="h-10" key={`st-v3-${_id}`}> <Tr className="h-10" key={`st-v3-${_id}`}>
<Td>{name}</Td> <Td>{name}</Td>
<Td> <Td>
<Switch <ProjectPermissionCan
id={`enable-service-token-${_id}`} I={ProjectPermissionActions.Edit}
onCheckedChange={(value) => handleToggleServiceTokenDataStatus({ a={ProjectPermissionSub.ServiceTokens}
serviceTokenDataId: _id,
isActive: value
})}
isChecked={isActive}
> >
<p className="w-12 mr-4">{isActive ? "Active" : "Inactive"}</p> {(isAllowed) => (
</Switch> <Switch
id={`enable-service-token-${_id}`}
onCheckedChange={(value) => handleToggleServiceTokenDataStatus({
serviceTokenDataId: _id,
isActive: value
})}
isChecked={isActive}
isDisabled={!isAllowed}
>
<p className="w-12 mr-4">{isActive ? "Active" : "Inactive"}</p>
</Switch>
)}
</ProjectPermissionCan>
</Td> </Td>
<Td> <Td>
{scopes.map((scope) => { {scopes.map((scope) => {