Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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