Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
b3e7a3c
feat: add createdAt timestamp to api key model
kworathur Mar 9, 2026
a9f51b3
fix: TypeError in auth_service
kworathur Mar 9, 2026
f361488
feat: add timestamp when converting api key to ts
kworathur Mar 9, 2026
d8644fe
feat: modify service defn to include list api key rpc
kworathur Mar 9, 2026
660aff9
feat: define additional routes for listing, deleting api keys
kworathur Mar 9, 2026
6581196
implement list operations
kworathur Mar 9, 2026
6fcb17e
feat: refactor list API keys endpoint to accept required projectId pa…
kworathur Mar 10, 2026
f5a725e
test: new api key crud methods and endpoints
kworathur Mar 10, 2026
9bc2c68
feat: refactor list api keys endpoint response to be project-scoped
kworathur Mar 10, 2026
fd94f0f
feat: move list api keys by project to project module
kworathur Mar 10, 2026
a783ac9
fix: cast project id from long to id
kworathur Mar 10, 2026
b7862f7
fix: project id not being casted to number in api gateway response
kworathur Mar 10, 2026
8667f56
fix: ensure users cant delete api keys for project they are not admin…
kworathur Mar 10, 2026
5d58d3c
fix: ensure project admins can only list api keys for their projects
kworathur Mar 11, 2026
dfd0209
test: list API keys methods do not return api keys when user not link…
kworathur Mar 15, 2026
2da8da9
Merge branch 'main' into feat/69-spring-2026-api-key-creation-page
kworathur Mar 15, 2026
0dbf8e2
Update packages/db-service/src/modules/auth/auth.service.ts
kworathur Mar 15, 2026
a9015dc
Update packages/api-gateway/src/modules/auth/auth.controller.ts
kworathur Mar 15, 2026
30f68e9
fix: non-admin users can't view api keys
kworathur Mar 15, 2026
f44c481
styles: fix lint errors
kworathur Mar 15, 2026
c5f0ba2
fix(auth): add deleteApiKey RPC and fix getAllApiKeys 500 errors
kworathur Mar 16, 2026
e306900
fix: use positive default value for limit
kworathur Mar 22, 2026
84b66db
fix: use jwt for authentication rather than email/pass
kworathur Mar 23, 2026
3f1ae5b
feat(auth): use JWT auth instead of email/password
kworathur Mar 27, 2026
8403df6
fix: revokeApiKey does not delete API key
kworathur Mar 27, 2026
38ae137
fix(auth): revert createApiKey to email/passwd auth
kworathur Apr 1, 2026
298f648
docs: make important clarification about user JWT purpose in juno
kworathur Apr 1, 2026
84bc3d7
fix: use api key middleware for list and delete api keys
kworathur Apr 1, 2026
0e2ad57
fix: failing api key tests
kworathur Apr 2, 2026
93cd789
fix: remove redundant delete by id route
kworathur Apr 3, 2026
b6a4c0e
fix: OpenAPI schema validation errors
kworathur Apr 3, 2026
8964c63
fix: api key response schema types
kworathur Apr 3, 2026
4a6c613
feat: define correct schema for api key in get api keys response
kworathur Apr 3, 2026
dc7cbc7
fix: restore delete api key by id
kworathur Apr 3, 2026
acde3cf
fix(tests): fix 500 errors in tests
kworathur Apr 3, 2026
942b6e3
tests: remove redundant project api key tests
kworathur Apr 3, 2026
e6adcab
chore: clean up unused imports
kworathur Apr 3, 2026
76e61b0
Update packages/api-gateway/src/modules/auth/auth.controller.ts
kworathur Apr 3, 2026
7d2e67d
Update packages/api-gateway/test/auth.e2e-spec.ts
kworathur Apr 3, 2026
cb3de08
Update packages/api-gateway/src/modules/auth/auth.controller.ts
kworathur Apr 3, 2026
2d54c72
Update packages/api-gateway/src/modules/auth/auth.controller.ts
kworathur Apr 3, 2026
9844201
Update packages/api-gateway/src/modules/auth/auth.controller.ts
kworathur Apr 3, 2026
a5a10ea
fix: unecessary get all projects call for superadmins
kworathur Apr 3, 2026
d38c62f
update protos
aakashg00 Apr 12, 2026
9380782
fix: update sdk gen script
llam36 Apr 12, 2026
ab0a06f
fix: revert changes
llam36 Apr 12, 2026
7392839
fix pagination
aakashg00 Apr 12, 2026
2aaa309
Merge branch 'feat/69-spring-2026-api-key-creation-page' of github.co…
aakashg00 Apr 12, 2026
826da89
fix: remove unuse client
llam36 Apr 12, 2026
3f64862
remove unused stuff
llam36 Apr 12, 2026
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
47 changes: 22 additions & 25 deletions packages/api-gateway/src/middleware/api_key.middleware.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
Injectable,
Logger,
NestMiddleware,
Inject,
OnModuleInit,
Expand All @@ -20,6 +21,7 @@ const { JWT_SERVICE_NAME } = JwtProto;

@Injectable()
export class ApiKeyMiddleware implements NestMiddleware, OnModuleInit {
private readonly logger = new Logger(ApiKeyMiddleware.name);
private apiKeyService: ApiKeyProto.ApiKeyServiceClient;
private jwtService: JwtProto.JwtServiceClient;

Expand Down Expand Up @@ -58,19 +60,7 @@ export class ApiKeyMiddleware implements NestMiddleware, OnModuleInit {
});
const res = await lastValueFrom(apiKeyValidation);
req.apiKey = res.key;

const userJwt = req.headers['x-user-jwt'] as string | undefined;
if (userJwt) {
try {
const userJwtValidation = this.jwtService.validateUserJwt({
jwt: userJwt,
});
const userJwtRes = await lastValueFrom(userJwtValidation);
if (userJwtRes.valid && userJwtRes.user) {
req.user = userJwtRes.user;
}
} catch (error) {}
}
await this.resolveUserFromJwt(req);

next();
} catch (error) {
Expand All @@ -85,18 +75,7 @@ export class ApiKeyMiddleware implements NestMiddleware, OnModuleInit {

req.apiKey = jwtRes.apiKey;

const userJwt = req.headers['x-user-jwt'] as string | undefined;
if (userJwt) {
try {
const userJwtValidation = this.jwtService.validateUserJwt({
jwt: userJwt,
});
const userJwtRes = await lastValueFrom(userJwtValidation);
if (userJwtRes.valid && userJwtRes.user) {
req.user = userJwtRes.user;
}
} catch (error) {}
}
await this.resolveUserFromJwt(req);

next();
} catch (error) {
Expand All @@ -105,6 +84,24 @@ export class ApiKeyMiddleware implements NestMiddleware, OnModuleInit {
}
}

private async resolveUserFromJwt(req: ApiKeyReq): Promise<void> {
const userJwt = req.headers['x-user-jwt'] as string | undefined;
if (!userJwt) return;

try {
const result = await lastValueFrom(
this.jwtService.validateUserJwt({ jwt: userJwt }),
);
if (result.valid && result.user) {
req.user = result.user;
}
} catch (error) {
this.logger.warn(
`x-user-jwt validation failed: ${(error as Error).message}`,
);
}
}

private extractTokenFromHeader(request: Request): string | undefined {
const [type, token] = request.headers.authorization?.split(' ') ?? [];
return type === 'Bearer' ? token : undefined;
Expand Down
167 changes: 161 additions & 6 deletions packages/api-gateway/src/models/auth.dto.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,124 @@
import { ApiProperty } from '@nestjs/swagger';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsNotEmpty } from 'class-validator';
import { ApiKeyProto, IdentifierProto, JwtProto } from 'juno-proto';
import {
ApiKeyProto,
AuthCommonProto,
IdentifierProto,
JwtProto,
} from 'juno-proto';

export class IssueApiKeyRequest {
@ApiProperty({ description: 'Optional description for key' })
@ApiPropertyOptional({
description: 'Optional description for key',
example: 'Production API key for mobile app',
})
description?: string | undefined;

@ApiProperty({ description: 'Environment the key will be tied to' })
@ApiProperty({
description: 'Environment the key will be tied to',
example: 'production',
})
@IsNotEmpty()
environment: string;

@IsNotEmpty()
@ApiProperty({ description: 'Project identifier' })
@ApiProperty({
description: 'Project identifier',
example: { name: 'my-project' },
})
project: IdentifierProto.ProjectIdentifier;
}

export class IssueApiKeyResponse {
@ApiProperty({ type: 'string', description: 'The generated API key' })
@ApiProperty({
description:
'The generated API key value (store immediately, not retrievable again)',
example: 'a1b2c3d4e5f6...',
})
apiKey: string;

@ApiProperty({
description: 'Environment this key was issued for',
example: 'production',
})
environment: string;

@ApiProperty({
description: 'Description provided at creation',
example: 'Production API key for mobile app',
})
description: string;

@ApiProperty({
description: 'ISO timestamp of key creation',
example: '2026-01-01T00:00:00.000Z',
})
createdAt: string;

@ApiProperty({ description: 'project identifier for the API key' })
project: string;

constructor(res: ApiKeyProto.IssueApiKeyResponse) {
this.apiKey = res.apiKey;
this.environment = res.info?.environment;
this.description = res.info?.description;
this.createdAt = res.info?.createdAt;
this.project = res.info.project.id.toString();
}
}

export class ApiKey {
@ApiProperty({
description: "The API key's ID in the databse",
example: '5',
})
id: string;

@ApiProperty({
description:
'The generated API key value (store immediately, not retrievable again)',
example: 'a1b2c3d4e5f6...',
})
hash: string;

@ApiProperty({
description: 'Description provided at creation',
example: 'Production API key for mobile app',
})
description: string;

@ApiProperty({
description: 'Scopes tied to this API key',
example: ['read:projects', 'write:analytics'],
type: [String],
})
scopes: string[];

@ApiProperty({ description: 'project identifier for the API key' })
project: string;

@ApiProperty({
description: 'Environment this key was issued for',
example: 'production',
})
environment: string;

@ApiProperty({
description: 'ISO timestamp of key creation',
example: '2026-01-01T00:00:00.000Z',
})
createdAt: string;

constructor(res: AuthCommonProto.ApiKey) {
this.id = res.id;
this.hash = res.hash;
this.description = res.description;
this.scopes = res.scopes
? res.scopes.map((scope) => scope.toString())
: [''];
this.environment = res.environment;
this.project = res.project.id.toString();
this.createdAt = res.createdAt;
}
}

Expand All @@ -32,3 +130,60 @@ export class IssueJWTResponse {
this.token = res.jwt;
}
}

export class PaginationParams {
@ApiProperty({
description: 'first page of results',
example: '/auth/key/?offset=0&limit=5',
})
first: string;
@ApiProperty({
description: 'previous page of results',
example: '/auth/key/?offset=15&limit=5',
})
prev: string;
@ApiProperty({
description: 'next page of results',
example: '/auth/key/?offset=20&limit=5',
})
next: string;
@ApiProperty({
description: 'last page of results',
example: '/auth/key/?offset=25&limit=5',
})
last: string;

constructor(res: {
first: string;
prev: string;
next: string;
last: string;
}) {
this.first = res.first;
this.prev = res.prev;
this.next = res.next;
this.last = res.last;
}
}
export class GetAllApiKeysResponse {
@ApiProperty({
type: ApiKey,
isArray: true,
description: 'List of API keys belonging to a project',
})
keys: ApiKey[];

@ApiProperty({
type: PaginationParams,
description: 'Pagination parameters',
})
links: PaginationParams;

constructor(res: {
keys: AuthCommonProto.ApiKey[];
links: PaginationParams;
}) {
this.keys = (res.keys ?? []).map((key) => new ApiKey(key));
this.links = res.links;
}
}
Loading
Loading