Skip to content
Merged
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
32 changes: 31 additions & 1 deletion client/src/components/loginprompt.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@
max-width="100"
class="mx-auto"
></v-img>


<!-- Show info panel if domain is demo.kubero.dev -->
<v-alert
v-if="isDemoDomain"
type="info"
border="start"
class="mt-5"
>
User: <b>demo/reader</b><br>
Pass: <b>123456</b>
</v-alert>
<div v-if="authMethods.local" class="py-5">
<v-alert
v-show="error"
Expand All @@ -28,11 +40,13 @@
</v-alert>
<form v-on:submit="login">
<v-text-field
v-model="username"
label="username"
name="username"
required
></v-text-field>
<v-text-field
v-model="password"
label="password"
type="password"
name="password"
Expand Down Expand Up @@ -83,7 +97,6 @@
</template>

<script lang="ts">
import router from "../router"
import axios from "axios"
import { defineComponent } from 'vue'

Expand All @@ -95,12 +108,29 @@ export default defineComponent({
data: () => ({
error: false,
errorshake: false,
username: '',
password: '',
authMethods : {
"local": false,
"github": false,
"oauth2": false
}
}),
computed: {
isDemoDomain(): boolean {
const demoDomains = [
'demo.kubero.dev',
//'kubero.localhost',
'localhost'
];
const demoDomain = demoDomains.includes(window.location.hostname)
if (demoDomain) {
this.username = 'demo';
this.password = '123456';
}
return demoDomain;
}
},
mounted() {
this.getAuthMethods();
},
Expand Down
4 changes: 3 additions & 1 deletion server/.env.template
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ KUBERO_SESSION_KEY=randomString
KUBERO_CLUSTERISSUER=letsencrypt-prod
KUBERO_BUILD_REGISTRY=kubero-registry-yourdomain.com/something

KUBERO_PROMETHEUS_ENDPOINT=http://prometheus.localhost
# KUBERO_PROMETHEUS_ENDPOINT=http://kubero-prometheus-server # within cluster
KUBERO_PROMETHEUS_ENDPOINT=http:/127.0.0.1:8080 # for local development
# kubectl port-forward svc/kubero-prometheus-server 8080:80 -n kubero

KUBERO_AUDIT=false
KUBERO_AUDIT_DB_PATH=./db
Expand Down
10 changes: 10 additions & 0 deletions server/src/auth/permissions.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@ export class PermissionsGuard implements CanActivate {
context.getHandler(),
context.getClass(),
]);
/* Disabling for RBAC
if (
!process.env.KUBERO_USERS &&
!process.env.GITHUB_CLIENT_SECRET &&
!process.env.OAUTH2_CLIENT_SECRET
) {
return true;
}
*/

Comment on lines +14 to +23

Copilot AI Jul 15, 2025

Copy link

Choose a reason for hiding this comment

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

[nitpick] This commented-out RBAC fallback adds noise; removing deprecated logic would improve readability.

Suggested change
/* Disabling for RBAC
if (
!process.env.KUBERO_USERS &&
!process.env.GITHUB_CLIENT_SECRET &&
!process.env.OAUTH2_CLIENT_SECRET
) {
return true;
}
*/

Copilot uses AI. Check for mistakes.
if (!requiredPermissions || requiredPermissions.length === 0) {
return true;
}
Expand Down
3 changes: 3 additions & 0 deletions server/src/auth/strategies/jwt.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,16 @@ export class JwtAuthGuard extends AuthGuard('jwt') {

handleRequest(err, user, info) {
// Disabling authentication when no auth method is defined

/* Disabling for RBAC
if (
!process.env.KUBERO_USERS &&
!process.env.GITHUB_CLIENT_SECRET &&
!process.env.OAUTH2_CLIENT_SECRET
) {
return true;
}
*/
Comment on lines +23 to +31

Copilot AI Jul 15, 2025

Copy link

Choose a reason for hiding this comment

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

[nitpick] Rather than leaving large commented-out blocks, consider removing this legacy code to keep the guard implementation clean.

Suggested change
/* Disabling for RBAC
if (
!process.env.KUBERO_USERS &&
!process.env.GITHUB_CLIENT_SECRET &&
!process.env.OAUTH2_CLIENT_SECRET
) {
return true;
}
*/
// Removed legacy code for RBAC disabling.

Copilot uses AI. Check for mistakes.

if (err || !user) {
//this.logger.debug('JwtAuthGuard.handleRequest Error', err, user, info);
Expand Down
4 changes: 4 additions & 0 deletions server/src/common/guards/readonly.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
Logger,
} from '@nestjs/common';

/* DEPRECATED: This guard is deprecated and will be removed in future versions in favour of Kubero roles*/
@Injectable()
export class ReadonlyGuard implements CanActivate {
private logger = new Logger(ReadonlyGuard.name);
Expand All @@ -14,6 +15,9 @@ export class ReadonlyGuard implements CanActivate {
this.logger.warn(
'Kubero is in read-only mode, write operations are blocked',
);
this.logger.warn(
'KUBERO_READONLY is deprecated! Use Kubero\'s RBAC feature instead.',
);
throw new HttpException('Kubero is in read-only mode', 202);
}
return true;
Expand Down
20 changes: 20 additions & 0 deletions server/src/deployments/deployments.service.spec.ts.old
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Test, TestingModule } from '@nestjs/testing';
import { DeploymentsController } from './deployments.controller';
import { DeploymentsService } from './deployments.service';

describe('DeploymentsService', () => {
let service: DeploymentsService;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [DeploymentsController],
providers: [{ provide: DeploymentsService, useValue: {} }],
}).compile();

service = module.get<DeploymentsService>(DeploymentsService);
});

it('should be defined', () => {
expect(service).toBeDefined();
});
});
7 changes: 4 additions & 3 deletions server/src/groups/groups.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { OKDTO } from '../common/dto/ok.dto';
import { GroupsService } from './groups.service';
import { PermissionsGuard } from '../auth/permissions.guard';
import { Permissions } from '../auth/permissions.decorator';
import { ReadonlyGuard } from '../common/guards/readonly.guard';

@Controller({ path: 'api/groups', version: '1' })
export class GroupsController {
Expand All @@ -44,7 +45,7 @@ export class GroupsController {
}

@Post('/')
@UseGuards(JwtAuthGuard, PermissionsGuard)
@UseGuards(JwtAuthGuard, PermissionsGuard, ReadonlyGuard)
@Permissions('user:write')
@ApiBearerAuth('bearerAuth')
@ApiForbiddenResponse({
Expand All @@ -66,7 +67,7 @@ export class GroupsController {
}

@Delete('/:id')
@UseGuards(JwtAuthGuard, PermissionsGuard)
@UseGuards(JwtAuthGuard, PermissionsGuard, ReadonlyGuard)
@Permissions('user:write')
@ApiBearerAuth('bearerAuth')
@ApiForbiddenResponse({
Expand All @@ -88,7 +89,7 @@ export class GroupsController {
}

@Put('/:id')
@UseGuards(JwtAuthGuard, PermissionsGuard)
@UseGuards(JwtAuthGuard, PermissionsGuard, ReadonlyGuard)
@Permissions('user:write')
@ApiBearerAuth('bearerAuth')
@ApiForbiddenResponse({
Expand Down
7 changes: 4 additions & 3 deletions server/src/roles/roles.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { OKDTO } from '../common/dto/ok.dto';
import { RolesService } from './roles.service';
import { PermissionsGuard } from '../auth/permissions.guard';
import { Permissions } from '../auth/permissions.decorator';
import { ReadonlyGuard } from '../common/guards/readonly.guard';

@Controller({ path: 'api/roles', version: '1' })
export class RolesController {
Expand All @@ -44,7 +45,7 @@ export class RolesController {
}

@Post('/')
@UseGuards(JwtAuthGuard, PermissionsGuard)
@UseGuards(JwtAuthGuard, PermissionsGuard, ReadonlyGuard)
@Permissions('user:write')
@ApiBearerAuth('bearerAuth')
@ApiForbiddenResponse({
Expand All @@ -63,7 +64,7 @@ export class RolesController {
}

@Delete('/:roleId')
@UseGuards(JwtAuthGuard, PermissionsGuard)
@UseGuards(JwtAuthGuard, PermissionsGuard, ReadonlyGuard)
@Permissions('user:write')
@ApiBearerAuth('bearerAuth')
@ApiForbiddenResponse({
Expand All @@ -82,7 +83,7 @@ export class RolesController {
}

@Put('/:roleId')
@UseGuards(JwtAuthGuard, PermissionsGuard)
@UseGuards(JwtAuthGuard, PermissionsGuard, ReadonlyGuard)
@Permissions('user:write')
@ApiBearerAuth('bearerAuth')
@ApiForbiddenResponse({
Expand Down
8 changes: 4 additions & 4 deletions server/src/token/token.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { OKDTO } from '../common/dto/ok.dto';
import { TokenService } from './token.service';
import { PermissionsGuard } from '../auth/permissions.guard';
import { Permissions } from '../auth/permissions.decorator';

import { ReadonlyGuard } from '../common/guards/readonly.guard';

@Controller({ path: 'api/tokens', version: '1' })
export class TokenController {
Expand Down Expand Up @@ -69,7 +69,7 @@ export class TokenController {
}
*/
@Post('/my')
@UseGuards(JwtAuthGuard, PermissionsGuard)
@UseGuards(JwtAuthGuard, PermissionsGuard, ReadonlyGuard)
@Permissions('token:ok', 'token:write')
@ApiBearerAuth('bearerAuth')
@ApiForbiddenResponse({
Expand Down Expand Up @@ -107,7 +107,7 @@ export class TokenController {
}

@Delete('/:id')
@UseGuards(JwtAuthGuard, PermissionsGuard)
@UseGuards(JwtAuthGuard, PermissionsGuard, ReadonlyGuard)
@Permissions('token:ok', 'token:write')
@ApiBearerAuth('bearerAuth')
@ApiForbiddenResponse({
Expand Down Expand Up @@ -154,7 +154,7 @@ export class TokenController {
}

@Delete('/my/:id')
@UseGuards(JwtAuthGuard, PermissionsGuard)
@UseGuards(JwtAuthGuard, PermissionsGuard, ReadonlyGuard)
@Permissions('token:ok', 'token:write')
@ApiBearerAuth('bearerAuth')
@ApiForbiddenResponse({
Expand Down
51 changes: 9 additions & 42 deletions server/src/users/users.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { GetAllUsersDTO } from './dto/users.dto';
import { FileInterceptor } from '@nestjs/platform-express';
import { PermissionsGuard } from '../auth/permissions.guard';
import { Permissions } from '../auth/permissions.decorator';
import { ReadonlyGuard } from '../common/guards/readonly.guard';

@Controller({ path: 'api/users', version: '1' })
export class UsersController {
Expand Down Expand Up @@ -110,7 +111,7 @@ export class UsersController {

@Get('/count')
@UseGuards(JwtAuthGuard, PermissionsGuard)
@Permissions('user:read', 'user:write')
@Permissions('user:write')
@ApiBearerAuth('bearerAuth')
@ApiForbiddenResponse({
description: 'Error: Unauthorized',
Expand All @@ -128,7 +129,7 @@ export class UsersController {
}

@Put('/:id')
@UseGuards(JwtAuthGuard, PermissionsGuard)
@UseGuards(JwtAuthGuard, PermissionsGuard, ReadonlyGuard)
@Permissions('user:write')
@ApiBearerAuth('bearerAuth')
@ApiForbiddenResponse({
Expand All @@ -147,7 +148,7 @@ export class UsersController {
}

@Delete('/:id')
@UseGuards(JwtAuthGuard, PermissionsGuard)
@UseGuards(JwtAuthGuard, PermissionsGuard, ReadonlyGuard)
@Permissions('user:write')
@ApiBearerAuth('bearerAuth')
@ApiForbiddenResponse({
Expand All @@ -166,7 +167,7 @@ export class UsersController {
}

@Put('/:id/password/')
@UseGuards(JwtAuthGuard, PermissionsGuard)
@UseGuards(JwtAuthGuard, PermissionsGuard, ReadonlyGuard)
@Permissions('user:write')
@ApiBearerAuth('bearerAuth')
@ApiForbiddenResponse({
Expand Down Expand Up @@ -198,7 +199,7 @@ export class UsersController {
}

@Put('/update-my-password')
@UseGuards(JwtAuthGuard)
@UseGuards(JwtAuthGuard, ReadonlyGuard)
@ApiBearerAuth('bearerAuth')
@ApiForbiddenResponse({
description: 'Error: Unauthorized',
Expand Down Expand Up @@ -230,7 +231,8 @@ export class UsersController {
}

@Post('/')
@UseGuards(JwtAuthGuard)
@UseGuards(JwtAuthGuard, PermissionsGuard, ReadonlyGuard)
@Permissions('user:write')
@ApiBearerAuth('bearerAuth')
@ApiForbiddenResponse({
description: 'Error: Unauthorized',
Expand Down Expand Up @@ -274,7 +276,7 @@ export class UsersController {
}

@Post('/profile/avatar')
@UseGuards(JwtAuthGuard)
@UseGuards(JwtAuthGuard, ReadonlyGuard)
@UseInterceptors(FileInterceptor('avatar'))
@ApiBearerAuth('bearerAuth')
@ApiForbiddenResponse({
Expand Down Expand Up @@ -305,39 +307,4 @@ export class UsersController {
}
return this.usersService.updateAvatar(user.userId, file);
}
/*
@Get('/profile/avatar')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth('bearerAuth')
@ApiForbiddenResponse({
description: 'Error: Unauthorized',
type: OKDTO,
isArray: false,
})
@ApiOkResponse({
description: 'Get current User avatar',
type: GetAllUsersDTO,
isArray: false,
})
@ApiOperation({ summary: 'Get current User avatar' })
async getProfileAvatar(@Request() req: any, @Response() res: ResType) {
const user = req.user;
const avatarImage = await this.usersService.getAvatar(user.userId);

if (!avatarImage) {
throw new HttpException('No avatar image found', HttpStatus.NOT_FOUND);
}

// Parse data URL: data:[<mediatype>][;base64],<data>
const matches = avatarImage.match(/^data:(.+);base64,(.+)$/);
if (!matches || matches.length !== 3) {
throw new HttpException('Invalid avatar image format', HttpStatus.INTERNAL_SERVER_ERROR);
}
const contentType = matches[1];
const imageBuffer = Buffer.from(matches[2], 'base64');
res.setHeader('Content-Type', contentType);
res.setHeader('Content-Length', imageBuffer.length);
return res.end(imageBuffer);
}
*/
}
Loading