fix some middleware nonsense

This commit is contained in:
Evert Prants 2022-09-01 20:19:08 +03:00
parent 564f3427a4
commit b02191608a
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
11 changed files with 139 additions and 25 deletions

View File

@ -44,6 +44,7 @@ async function bootstrap() {
} }
app.use( app.use(
/\/((?!api).)*/,
session({ session({
secret: process.env.SESSION_SECRET, secret: process.env.SESSION_SECRET,
resave: true, resave: true,

View File

@ -48,6 +48,7 @@ const SET_CLIENT_FIELDS = [
'grants', 'grants',
'activated', 'activated',
'verified', 'verified',
'urls',
]; ];
const URL_TYPES = ['redirect_uri', 'terms', 'privacy', 'website']; const URL_TYPES = ['redirect_uri', 'terms', 'privacy', 'website'];
@ -167,6 +168,10 @@ export class OAuth2AdminController {
@CurrentUser() user: User, @CurrentUser() user: User,
) { ) {
const client = await this._oaClient.getById(parseInt(id, 10), []); const client = await this._oaClient.getById(parseInt(id, 10), []);
const reducedPermissions = !this._service.userHasPrivilege(
user,
'admin:oauth2',
);
if (!client) { if (!client) {
throw new NotFoundException('Client not found'); throw new NotFoundException('Client not found');
} }
@ -178,9 +183,11 @@ export class OAuth2AdminController {
} }
const allowedFieldsOnly = this._form.pluckObject(setter, SET_CLIENT_FIELDS); const allowedFieldsOnly = this._form.pluckObject(setter, SET_CLIENT_FIELDS);
const urls = allowedFieldsOnly.urls?.slice();
delete allowedFieldsOnly.urls;
// Non-admin cannot change the activated or verified status // Non-admin cannot change the activated or verified status
if (!this._service.userHasPrivilege(user, 'admin:oauth2')) { if (reducedPermissions) {
delete allowedFieldsOnly.activated; delete allowedFieldsOnly.activated;
delete allowedFieldsOnly.verified; delete allowedFieldsOnly.verified;
} }
@ -189,9 +196,33 @@ export class OAuth2AdminController {
return this._oaClient.stripClientInfo(client); return this._oaClient.stripClientInfo(client);
} }
const splitGrants = allowedFieldsOnly.grants.split(' ');
const splitScopes = allowedFieldsOnly.scope.split(' ');
let availableGrantTypes = this._oaClient.availableGrantTypes;
let availableScopes = this._oaClient.availableScopes;
if (reducedPermissions) {
availableGrantTypes =
this._service.removeUnprivileged(availableGrantTypes);
availableScopes = this._service.removeUnprivileged(availableScopes);
allowedFieldsOnly.activated = true;
}
if (!splitGrants.every((grant) => availableGrantTypes.includes(grant))) {
throw new BadRequestException('Bad grant types');
}
if (!splitScopes.every((scope) => availableScopes.includes(scope))) {
throw new BadRequestException('Bad scopes');
}
Object.assign(client, allowedFieldsOnly); Object.assign(client, allowedFieldsOnly);
await this._oaClient.updateClient(client); await this._oaClient.updateClient(client);
if (urls?.length) {
await this._oaClient.upsertURLs(client, urls);
}
return this._oaClient.stripClientInfo(client); return this._oaClient.stripClientInfo(client);
} }
@ -484,6 +515,9 @@ export class OAuth2AdminController {
throw new BadRequestException('Bad scopes'); throw new BadRequestException('Bad scopes');
} }
const urls = setter.urls?.slice();
delete allowedFieldsOnly.urls;
const client = new OAuth2Client(); const client = new OAuth2Client();
Object.assign(client, allowedFieldsOnly); Object.assign(client, allowedFieldsOnly);
client.client_id = this._token.createUUID(); client.client_id = this._token.createUUID();
@ -491,6 +525,10 @@ export class OAuth2AdminController {
client.owner = user; client.owner = user;
await this._oaClient.updateClient(client); await this._oaClient.updateClient(client);
if (urls?.length) {
await this._oaClient.upsertURLs(client, urls);
}
return this._oaClient.stripClientInfo(client); return this._oaClient.stripClientInfo(client);
} }
} }

View File

@ -6,6 +6,7 @@ import {
Get, Get,
NotFoundException, NotFoundException,
Param, Param,
Patch,
Post, Post,
Put, Put,
Query, Query,
@ -18,6 +19,7 @@ import { OAuth2Guard } from 'src/guards/oauth2.guard';
import { PrivilegesGuard } from 'src/guards/privileges.guard'; import { PrivilegesGuard } from 'src/guards/privileges.guard';
import { ScopesGuard } from 'src/guards/scopes.guard'; import { ScopesGuard } from 'src/guards/scopes.guard';
import { PrivilegeService } from 'src/modules/objects/privilege/privilege.service'; import { PrivilegeService } from 'src/modules/objects/privilege/privilege.service';
import { User } from 'src/modules/objects/user/user.entity';
import { UserService } from 'src/modules/objects/user/user.service'; import { UserService } from 'src/modules/objects/user/user.service';
import { FormUtilityService } from 'src/modules/utility/services/form-utility.service'; import { FormUtilityService } from 'src/modules/utility/services/form-utility.service';
import { PaginationService } from 'src/modules/utility/services/paginate.service'; import { PaginationService } from 'src/modules/utility/services/paginate.service';
@ -80,6 +82,32 @@ export class UserAdminController {
return this._form.stripObject(user, ['password']); return this._form.stripObject(user, ['password']);
} }
@Patch(':id')
@Scopes('management')
@Privileges('admin', 'admin:user')
async updateUser(@Param('id') id: string, @Body() body: Partial<User>) {
const user = await this._user.getById(parseInt(id, 10));
if (!user) {
throw new NotFoundException('User not found');
}
const allowedFieldsOnly = this._form.pluckObject(body, [
'display_name',
'username',
'email',
'activated',
]);
if (!Object.keys(allowedFieldsOnly).length) {
throw new BadRequestException('No data was updated.');
}
Object.assign(user, allowedFieldsOnly);
await this._user.updateUser(user);
return user;
}
/** /**
* Delete a user avatar from the server * Delete a user avatar from the server
* @param id User ID * @param id User ID
@ -162,7 +190,7 @@ export class UserAdminController {
* @param id User ID * @param id User ID
* @returns Success or error * @returns Success or error
*/ */
@Post(':id/activate') @Post(':id/activation')
@Scopes('management') @Scopes('management')
@Privileges('admin', 'admin:user') @Privileges('admin', 'admin:user')
async activateUserEmail(@Param('id') id: string) { async activateUserEmail(@Param('id') id: string) {

View File

@ -69,9 +69,9 @@ export class OAuth2Service {
public clientService: OAuth2ClientService, public clientService: OAuth2ClientService,
public tokenService: OAuth2TokenService, public tokenService: OAuth2TokenService,
) { ) {
if (process.env.NODE_ENV === 'development') { // if (process.env.NODE_ENV === 'development') {
this.oauth.logger.setLogLevel('debug'); // this.oauth.logger.setLogLevel('debug');
} // }
} }
public splitScope(scope: string | string[]): string[] { public splitScope(scope: string | string[]): string[] {

View File

@ -168,6 +168,7 @@ export class OAuth2ClientService {
: undefined, : undefined,
skip: offset, skip: offset,
take: limit, take: limit,
order: { id: 'asc' },
relations, relations,
}); });
} }
@ -197,6 +198,7 @@ export class OAuth2ClientService {
): Promise<OAuth2Client[]> { ): Promise<OAuth2Client[]> {
return this.clientRepository.find({ return this.clientRepository.find({
where: { owner: { id: owner.id } }, where: { owner: { id: owner.id } },
order: { id: 'asc' },
relations, relations,
}); });
} }
@ -234,6 +236,37 @@ export class OAuth2ClientService {
})); }));
} }
public async upsertURLs(
client: OAuth2Client,
urls: OAuth2ClientURL[],
): Promise<OAuth2Client> {
const existingURLs = await this.getClientURLs(client.client_id);
const removed = [];
for (const existing of existingURLs) {
const alsoProvided = urls.find(({ id }) => id === existing.id);
if (alsoProvided && !!alsoProvided.url) {
Object.assign(existing, alsoProvided);
await this.updateClientURL(existing);
} else {
await this.deleteClientURL(existing);
removed.push(existing);
}
}
for (const newUrl of urls.filter((url) => !url.id)) {
const newUrlObject = this.reobjectifyURL(newUrl, client);
await this.updateClientURL(newUrlObject);
existingURLs.push(newUrlObject);
}
client.urls = existingURLs
.filter(({ id }) => !removed.some((removed) => removed.id === id))
.map((itm) => ({ ...itm, client: undefined }));
return client;
}
public async updateClient(client: OAuth2Client): Promise<OAuth2Client> { public async updateClient(client: OAuth2Client): Promise<OAuth2Client> {
await this.clientRepository.save(client); await this.clientRepository.save(client);
return client; return client;
@ -289,4 +322,14 @@ export class OAuth2ClientService {
: null, : null,
} as Partial<OAuth2Client>; } as Partial<OAuth2Client>;
} }
private reobjectifyURL(
input: Partial<OAuth2ClientURL>,
client: OAuth2Client,
): OAuth2ClientURL {
const reObjectifyURL = new OAuth2ClientURL();
Object.assign(reObjectifyURL, input);
reObjectifyURL.client = client;
return reObjectifyURL;
}
} }

View File

@ -4,7 +4,9 @@ import {
NestModule, NestModule,
RequestMethod, RequestMethod,
} from '@nestjs/common'; } from '@nestjs/common';
import { CSRFMiddleware } from 'src/middleware/csrf.middleware';
import { FlashMiddleware } from 'src/middleware/flash.middleware'; import { FlashMiddleware } from 'src/middleware/flash.middleware';
import { UserMiddleware } from 'src/middleware/user.middleware';
import { ValidateCSRFMiddleware } from 'src/middleware/validate-csrf.middleware'; import { ValidateCSRFMiddleware } from 'src/middleware/validate-csrf.middleware';
import { UserTokenModule } from 'src/modules/objects/user-token/user-token.module'; import { UserTokenModule } from 'src/modules/objects/user-token/user-token.module';
import { UserModule } from 'src/modules/objects/user/user.module'; import { UserModule } from 'src/modules/objects/user/user.module';
@ -16,6 +18,7 @@ import { LoginController } from './login.controller';
}) })
export class LoginModule implements NestModule { export class LoginModule implements NestModule {
configure(consumer: MiddlewareConsumer) { configure(consumer: MiddlewareConsumer) {
consumer.apply(CSRFMiddleware, UserMiddleware).forRoutes(LoginController);
consumer consumer
.apply(ValidateCSRFMiddleware) .apply(ValidateCSRFMiddleware)
.forRoutes({ path: 'login*', method: RequestMethod.POST }); .forRoutes({ path: 'login*', method: RequestMethod.POST });

View File

@ -1,5 +1,7 @@
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { AuthMiddleware } from 'src/middleware/auth.middleware'; import { AuthMiddleware } from 'src/middleware/auth.middleware';
import { CSRFMiddleware } from 'src/middleware/csrf.middleware';
import { UserMiddleware } from 'src/middleware/user.middleware';
import { ValidateCSRFMiddleware } from 'src/middleware/validate-csrf.middleware'; import { ValidateCSRFMiddleware } from 'src/middleware/validate-csrf.middleware';
import { OAuth2Module } from 'src/modules/oauth2/oauth2.module'; import { OAuth2Module } from 'src/modules/oauth2/oauth2.module';
import { OAuth2Service } from 'src/modules/oauth2/oauth2.service'; import { OAuth2Service } from 'src/modules/oauth2/oauth2.service';
@ -14,6 +16,7 @@ export class OAuth2RouterModule implements NestModule {
constructor(private _service: OAuth2Service) {} constructor(private _service: OAuth2Service) {}
configure(consumer: MiddlewareConsumer) { configure(consumer: MiddlewareConsumer) {
consumer.apply(CSRFMiddleware, UserMiddleware).forRoutes(OAuth2Controller);
consumer.apply(this._service.oauth.express()).forRoutes('oauth2/*'); consumer.apply(this._service.oauth.express()).forRoutes('oauth2/*');
consumer consumer
.apply(AuthMiddleware, ValidateCSRFMiddleware) .apply(AuthMiddleware, ValidateCSRFMiddleware)

View File

@ -4,7 +4,9 @@ import {
NestModule, NestModule,
RequestMethod, RequestMethod,
} from '@nestjs/common'; } from '@nestjs/common';
import { CSRFMiddleware } from 'src/middleware/csrf.middleware';
import { FlashMiddleware } from 'src/middleware/flash.middleware'; import { FlashMiddleware } from 'src/middleware/flash.middleware';
import { UserMiddleware } from 'src/middleware/user.middleware';
import { ValidateCSRFMiddleware } from 'src/middleware/validate-csrf.middleware'; import { ValidateCSRFMiddleware } from 'src/middleware/validate-csrf.middleware';
import { UserModule } from 'src/modules/objects/user/user.module'; import { UserModule } from 'src/modules/objects/user/user.module';
import { RegisterController } from './register.controller'; import { RegisterController } from './register.controller';
@ -15,6 +17,10 @@ import { RegisterController } from './register.controller';
}) })
export class RegisterModule implements NestModule { export class RegisterModule implements NestModule {
configure(consumer: MiddlewareConsumer) { configure(consumer: MiddlewareConsumer) {
consumer
.apply(CSRFMiddleware, UserMiddleware)
.forRoutes(RegisterController);
consumer consumer
.apply(ValidateCSRFMiddleware) .apply(ValidateCSRFMiddleware)
.forRoutes({ path: 'register*', method: RequestMethod.POST }); .forRoutes({ path: 'register*', method: RequestMethod.POST });

View File

@ -21,6 +21,8 @@ import { UserTokenModule } from 'src/modules/objects/user-token/user-token.modul
import { OAuth2Module } from '../../oauth2/oauth2.module'; import { OAuth2Module } from '../../oauth2/oauth2.module';
import { SettingsController } from './settings.controller'; import { SettingsController } from './settings.controller';
import { SettingsService } from './settings.service'; import { SettingsService } from './settings.service';
import { CSRFMiddleware } from 'src/middleware/csrf.middleware';
import { UserMiddleware } from 'src/middleware/user.middleware';
@Module({ @Module({
controllers: [SettingsController], controllers: [SettingsController],
@ -69,6 +71,9 @@ import { SettingsService } from './settings.service';
}) })
export class SettingsModule implements NestModule { export class SettingsModule implements NestModule {
configure(consumer: MiddlewareConsumer) { configure(consumer: MiddlewareConsumer) {
consumer
.apply(CSRFMiddleware, UserMiddleware)
.forRoutes(SettingsController);
consumer consumer
.apply(ValidateCSRFMiddleware) .apply(ValidateCSRFMiddleware)
.forRoutes( .forRoutes(

View File

@ -1,11 +1,4 @@
import { import { Module } from '@nestjs/common';
MiddlewareConsumer,
Module,
NestModule,
RequestMethod,
} from '@nestjs/common';
import { CSRFMiddleware } from 'src/middleware/csrf.middleware';
import { UserMiddleware } from 'src/middleware/user.middleware';
import { ConfigurationModule } from '../config/config.module'; import { ConfigurationModule } from '../config/config.module';
import { JWTModule } from '../jwt/jwt.module'; import { JWTModule } from '../jwt/jwt.module';
import { ObjectsModule } from '../objects/objects.module'; import { ObjectsModule } from '../objects/objects.module';
@ -32,15 +25,4 @@ import { TwoFactorModule } from './two-factor/two-factor.module';
providers: [], providers: [],
exports: [], exports: [],
}) })
export class StaticFrontEndModule implements NestModule { export class StaticFrontEndModule {}
configure(consumer: MiddlewareConsumer) {
consumer
.apply(CSRFMiddleware, UserMiddleware)
.exclude(
{ path: 'uploads*', method: RequestMethod.ALL },
{ path: 'public*', method: RequestMethod.ALL },
{ path: 'api*', method: RequestMethod.ALL },
)
.forRoutes('*');
}
}

View File

@ -1,6 +1,8 @@
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { AuthMiddleware } from 'src/middleware/auth.middleware'; import { AuthMiddleware } from 'src/middleware/auth.middleware';
import { CSRFMiddleware } from 'src/middleware/csrf.middleware';
import { FlashMiddleware } from 'src/middleware/flash.middleware'; import { FlashMiddleware } from 'src/middleware/flash.middleware';
import { UserMiddleware } from 'src/middleware/user.middleware';
import { UserTokenModule } from 'src/modules/objects/user-token/user-token.module'; import { UserTokenModule } from 'src/modules/objects/user-token/user-token.module';
import { UserModule } from 'src/modules/objects/user/user.module'; import { UserModule } from 'src/modules/objects/user/user.module';
import { TwoFactorController } from './two-factor.controller'; import { TwoFactorController } from './two-factor.controller';
@ -11,6 +13,9 @@ import { TwoFactorController } from './two-factor.controller';
}) })
export class TwoFactorModule implements NestModule { export class TwoFactorModule implements NestModule {
configure(consumer: MiddlewareConsumer) { configure(consumer: MiddlewareConsumer) {
consumer
.apply(CSRFMiddleware, UserMiddleware)
.forRoutes(TwoFactorController);
consumer.apply(AuthMiddleware).forRoutes('account/two-factor/activate'); consumer.apply(AuthMiddleware).forRoutes('account/two-factor/activate');
consumer.apply(FlashMiddleware).forRoutes('account/two-factor/activate'); consumer.apply(FlashMiddleware).forRoutes('account/two-factor/activate');
} }