Skip to content

Commit

Permalink
feat: add authentication
Browse files Browse the repository at this point in the history
  • Loading branch information
pkarolyi committed May 4, 2024
1 parent 701706d commit c03d4a8
Show file tree
Hide file tree
Showing 9 changed files with 89 additions and 33 deletions.
7 changes: 5 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
# s3 or local
# required: comma separated list of valid tokens (do not include spaces) eg. "token1,token2,token3"
AUTH_TOKENS=

# required: 's3' or 'local'
STORAGE_PROVIDER=

# required if provider is local
# required if provider is local eg. blobs will point to /garden-snail/blobs in the container
LOCAL_STORAGE_PATH=

# required if provider is s3
Expand Down
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ node dist/main
### Environment variables

```sh
# Set it to 's3' or 'local'
# required: comma separated list of valid tokens
AUTH_TOKENS=

# required: 's3' or 'local'
STORAGE_PROVIDER=

# Required if provider is local
Expand All @@ -41,15 +44,14 @@ S3_ENDPOINT=

## Notes

The `1.1.0` release is working and is compatible with the latest turborepo releases. Check the integration tests on the latest [workflow run](https://github.com/pkarolyi/garden-snail/actions/).
Check the integration tests on the [workflow runs](https://github.com/pkarolyi/garden-snail/actions/) for a given tag to check for compatibility.

This version **does not include any authorization or rate limiting functionality**. It is intended for internal deployments in organizations with external access controls.
The `1.1.0` release and releases prior to that **do not include any authorization or rate limiting functionality**.

## Roadmap

These are the things I will be working on in the coming weeks in no particular order:

- Authorization
- Rate limiting
- More providers
- Based on requests
3 changes: 3 additions & 0 deletions src/artifacts/artifacts.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,16 @@ import {
Put,
Query,
StreamableFile,
UseGuards,
} from "@nestjs/common";
import { StorageService } from "src/storage/storage.service";
import { Readable } from "stream";
import { ArtifactsGuard } from "./artifacts.guard";
import { GetArtifactRO, PutArtifactRO, StatusRO } from "./artifacts.interface";
import { ArtifactQueryTeamPipe } from "./artifacts.pipe";

@Controller({ path: "artifacts", version: "8" })
@UseGuards(ArtifactsGuard)
export class ArtifactsController {
private readonly logger = new Logger(ArtifactsController.name);

Expand Down
24 changes: 24 additions & 0 deletions src/artifacts/artifacts.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { CanActivate, ExecutionContext, Injectable } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { FastifyRequest } from "fastify";
import { ConfigurationSchema } from "src/config/configuration";

@Injectable()
export class ArtifactsGuard implements CanActivate {
constructor(
private readonly configService: ConfigService<ConfigurationSchema, true>,
) {}

canActivate(context: ExecutionContext) {
const request = context.switchToHttp().getRequest<FastifyRequest>();
const authHeader = request.headers.authorization;

if (!authHeader) return false;

// eg. "Bearer token123"
const token = authHeader.split(" ")[1];
const authConfig = this.configService.get("auth", { infer: true });

return authConfig.tokens.includes(token);
}
}
12 changes: 11 additions & 1 deletion src/config/configuration.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,21 @@ import { describe, expect, it } from "vitest";
import { validate } from "./configuration";

const validConfigurationLocal = {
AUTH_TOKENS: "token1,token2",
STORAGE_PROVIDER: "local",
LOCAL_STORAGE_PATH: "path",
};

const validConfigurationS3 = {
AUTH_TOKENS: "token1,token2",
STORAGE_PROVIDER: "s3",
S3_BUCKET: "bucket",
S3_ACCESS_KEY_ID: "accessKeyId",
S3_SECRET_ACCESS_KEY: "secretAccessKey",
S3_SESSION_TOKEN: "sessionToken",
S3_REGION: "region",
S3_FORCE_PATH_STYLE: "true",
S3_ENDPOINT: "endpoint",
};

describe("Configuration", () => {
Expand All @@ -33,6 +36,9 @@ describe("Configuration", () => {
it("should transform the configuration", () => {
const configuration = validate(validConfigurationLocal);
expect(configuration).toEqual({
auth: {
tokens: validConfigurationLocal.AUTH_TOKENS.split(","),
},
storage: {
provider: validConfigurationLocal.STORAGE_PROVIDER,
basePath: validConfigurationLocal.LOCAL_STORAGE_PATH,
Expand All @@ -57,6 +63,9 @@ describe("Configuration", () => {
it("should transform the configuration", () => {
const configuration = validate(validConfigurationS3);
expect(configuration).toEqual({
auth: {
tokens: validConfigurationS3.AUTH_TOKENS.split(","),
},
storage: {
provider: validConfigurationS3.STORAGE_PROVIDER,
bucket: validConfigurationS3.S3_BUCKET,
Expand All @@ -66,7 +75,8 @@ describe("Configuration", () => {
sessionToken: validConfigurationS3.S3_SESSION_TOKEN,
},
region: validConfigurationS3.S3_REGION,
forcePathStyle: Boolean(validConfigurationS3.S3_FORCE_PATH_STYLE),
endpoint: validConfigurationS3.S3_ENDPOINT,
forcePathStyle: validConfigurationS3.S3_FORCE_PATH_STYLE === "true",
},
});
});
Expand Down
63 changes: 37 additions & 26 deletions src/config/configuration.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,40 @@
import { z } from "zod";

const splitComma = (s: string) => s.split(",");

const configurationSchema = z
.union([
z.object({
STORAGE_PROVIDER: z.literal("s3"),
S3_BUCKET: z.string(),
S3_ACCESS_KEY_ID: z.string(),
S3_SECRET_ACCESS_KEY: z.string(),
S3_SESSION_TOKEN: z.string().optional(),
S3_REGION: z.string().default("us-east-1"),
S3_FORCE_PATH_STYLE: z.coerce.boolean().default(false),
S3_ENDPOINT: z.string().optional(),
}),
.intersection(
z.object({
STORAGE_PROVIDER: z.literal("local"),
LOCAL_STORAGE_PATH: z.string(),
AUTH_TOKENS: z
.string()
.transform(splitComma)
.pipe(z.string().min(1).array()),
}),
])
.transform((data) =>
data.STORAGE_PROVIDER === "local"
? {
storage: {
z.union([
z.object({
STORAGE_PROVIDER: z.literal("s3"),
S3_BUCKET: z.string(),
S3_ACCESS_KEY_ID: z.string(),
S3_SECRET_ACCESS_KEY: z.string(),
S3_SESSION_TOKEN: z.string().optional(),
S3_REGION: z.string().default("us-east-1"),
S3_FORCE_PATH_STYLE: z.enum(["true", "false"]).default("false"),
S3_ENDPOINT: z.string().optional(),
}),
z.object({
STORAGE_PROVIDER: z.literal("local"),
LOCAL_STORAGE_PATH: z.string(),
}),
]),
)
.transform((data) => {
const storage =
data.STORAGE_PROVIDER === "local"
? {
provider: data.STORAGE_PROVIDER,
basePath: data.LOCAL_STORAGE_PATH,
},
}
: {
storage: {
}
: {
provider: data.STORAGE_PROVIDER,
bucket: data.S3_BUCKET,
credentials: {
Expand All @@ -35,11 +43,14 @@ const configurationSchema = z
sessionToken: data.S3_SESSION_TOKEN,
},
region: data.S3_REGION,
forcePathStyle: data.S3_FORCE_PATH_STYLE,
forcePathStyle: data.S3_FORCE_PATH_STYLE === "true",
endpoint: data.S3_ENDPOINT,
},
},
);
};
return {
storage,
auth: { tokens: data.AUTH_TOKENS },
};
});

export type ConfigurationSchema = z.infer<typeof configurationSchema>;

Expand Down
1 change: 1 addition & 0 deletions test/app.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ describe("AppController (e2e)", () => {
const result = await app.inject({
method: "GET",
url: "/v8/artifacts/status",
headers: { authorization: "Bearer token" },
});
expect(result.statusCode).toEqual(200);
expect(result.json()).toEqual({ status: "enabled" });
Expand Down
1 change: 1 addition & 0 deletions test/vitest.config.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export default defineConfig({
globals: true,
root: "./",
env: {
AUTH_TOKENS: "token",
STORAGE_PROVIDER: "local",
LOCAL_STORAGE_PATH: "blobs",
},
Expand Down
1 change: 1 addition & 0 deletions vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export default defineConfig({
globals: true,
root: "./",
env: {
AUTH_TOKENS: "token",
STORAGE_PROVIDER: "local",
LOCAL_STORAGE_PATH: "blobs",
},
Expand Down

0 comments on commit c03d4a8

Please sign in to comment.