Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/jobs/dto/output-job-v4.dto.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { PartialType } from "@nestjs/swagger";
import { CreateJobDto } from "./create-job.dto";
import {
IsArray,
IsDateString,
IsObject,
IsOptional,
IsString,
IsArray,
} from "class-validator";
import { PartialOutputDatasetDto } from "src/datasets/dto/output-dataset.dto";

Expand Down
18 changes: 18 additions & 0 deletions src/jobs/jobs.controller.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,19 @@ export class JobsControllerUtils {
}
}

/**
* Extract the Bearer token from the Authorization header of the request.
*/
private extractBearerToken(request: Request): string | undefined {
const authHeader = request.headers?.authorization;
if (!authHeader) return undefined;
const parts = authHeader.split(" ");
if (parts.length === 2 && parts[0].toLowerCase() === "bearer") {
return parts[1];
}
return undefined;
}

/**
* Create job implementation
*/
Expand All @@ -530,6 +543,11 @@ export class JobsControllerUtils {
createJobDto,
request.user as JWTUser,
);
// Extract JWT from Authorization header
const accessToken = this.extractBearerToken(request);
if (accessToken) {
jobInstance.accessToken = accessToken;
}
// Allow actions to validate DTO
const jobConfig = this.getJobTypeConfiguration(createJobDto.type);
const validateContext = { request: createJobDto, env: process.env };
Expand Down
15 changes: 15 additions & 0 deletions src/jobs/schemas/job.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ export type JobDocument = JobClass & Document;
timestamps: true,
toJSON: {
getters: true,
transform: (_doc: Document, ret: Record<string, unknown>) => {
delete ret.accessToken;
return ret;
Comment on lines +15 to +17

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 suggestion (security): Current hiding of accessToken only covers toJSON; consider also protecting it at query/serialization level.

This only protects API responses using toJSON; the field is still included by default in queries and in toObject() results. If accessToken is sensitive, also mark it as non-selectable (e.g. select: false) or use an equivalent mechanism so it can’t be exposed via other serialization paths that bypass this transform.

Suggested implementation:

  timestamps: true,
  toJSON: {
    getters: true,
    transform: (_doc: Document, ret: Record<string, unknown>) => {
      delete ret.accessToken;
      return ret;
    },
  },
  toObject: {
    getters: true,
    transform: (_doc: Document, ret: Record<string, unknown>) => {
      delete ret.accessToken;
      return ret;
    },
  },
})
export class JobClass extends OwnableClass {

To fully implement the suggestion and protect accessToken at the query level, you should also mark the accessToken field as non-selectable in its @Prop definition. For example, if the field currently looks like:

  @Prop()
  accessToken: string;

you should change it to:

  @Prop({ select: false })
  accessToken: string;

or, if there are already options:

  @Prop({ type: String, select: false })
  accessToken: string;

This ensures accessToken is excluded by default from query results and all serialization paths, while still allowing explicit inclusion via .select('+accessToken') when needed.

},
},
})
export class JobClass extends OwnableClass {
Expand Down Expand Up @@ -106,6 +110,17 @@ export class JobClass extends OwnableClass {
default: {},
})
jobResultObject: Record<string, unknown>;

/**
* JWT access token provided by the user at job creation time.
* Stored for reuse by actions performed within the job.
* Not exposed in API responses for security reasons.
*/
@Prop({
type: String,
required: false,
})
accessToken?: string;
Comment on lines +119 to +123

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 issue (security): Storing raw JWTs in the database may not be necessary and increases the blast radius of a DB compromise.

Persisting the full access token means a DB leak exposes reusable credentials until expiry. If you only need it to call downstream services, consider storing a less-sensitive representation (e.g. minimal claims or a reference/ID) or shortening its lifetime. If the full token must be stored, consider additional at-rest protection for this field (e.g. encryption).

}
export const JobSchema = SchemaFactory.createForClass(JobClass);

Expand Down
Loading