mirror of
https://github.com/johrpan/musicus_mobile.git
synced 2025-10-26 10:47:25 +01:00
server: Protect routes with user authorization
This commit is contained in:
parent
ef40b4cd06
commit
7aecbbba69
14 changed files with 334 additions and 11 deletions
|
|
@ -15,5 +15,6 @@ Future<void> main() async {
|
||||||
);
|
);
|
||||||
|
|
||||||
print('Database: ${config.dbPath ?? 'memory'}');
|
print('Database: ${config.dbPath ?? 'memory'}');
|
||||||
|
print('Server database: ${config.serverDbPath ?? 'memory'}');
|
||||||
print('Listening on ${config.host}:${config.port}');
|
print('Listening on ${config.host}:${config.port}');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
# A dbPath of null means that we want an in-memory database.
|
# A dbPath and serverDbPath of null means that we want in-memory databases.
|
||||||
host: localhost
|
host: localhost
|
||||||
port: 1833
|
port: 1833
|
||||||
|
secret: vulnerable
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
host: localhost
|
host: localhost
|
||||||
port: 1833
|
port: 1833
|
||||||
dbPath: db.sqlite
|
secret: vulnerable
|
||||||
|
dbPath: db.sqlite
|
||||||
|
serverDbPath: server.sqlite
|
||||||
176
server/lib/src/auth.dart
Normal file
176
server/lib/src/auth.dart
Normal file
|
|
@ -0,0 +1,176 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:aqueduct/aqueduct.dart';
|
||||||
|
import 'package:corsac_jwt/corsac_jwt.dart';
|
||||||
|
import 'package:steel_crypt/steel_crypt.dart';
|
||||||
|
|
||||||
|
import 'database.dart';
|
||||||
|
|
||||||
|
/// Information on the rights of the user making the request.
|
||||||
|
extension AuthorizationInfo on Request {
|
||||||
|
/// Whether the user may create new resources.
|
||||||
|
set mayUpload(bool value) => this.attachments['mayUpload'] = value;
|
||||||
|
bool get mayUpload => this.attachments['mayUpload'] ?? false;
|
||||||
|
|
||||||
|
/// Whether the user may edit existing resources.
|
||||||
|
set mayEdit(bool value) => this.attachments['mayEdit'] = value;
|
||||||
|
bool get mayEdit => this.attachments['mayEdit'] ?? false;
|
||||||
|
|
||||||
|
/// Whether the user may delete resources.
|
||||||
|
set mayDelete(bool value) => this.attachments['mayDelete'] = value;
|
||||||
|
bool get mayDelete => this.attachments['mayDelete'] ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A user as presented within a request.
|
||||||
|
class RequestUser {
|
||||||
|
/// The unique user name.
|
||||||
|
final String name;
|
||||||
|
|
||||||
|
/// An optional email address.
|
||||||
|
final String email;
|
||||||
|
|
||||||
|
/// The password in clear text.
|
||||||
|
final String password;
|
||||||
|
|
||||||
|
RequestUser({
|
||||||
|
this.name,
|
||||||
|
this.email,
|
||||||
|
this.password,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory RequestUser.fromJson(Map<String, dynamic> json) => RequestUser(
|
||||||
|
name: json['name'],
|
||||||
|
email: json['email'],
|
||||||
|
password: json['password'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Endpoint controller for user registration.
|
||||||
|
///
|
||||||
|
/// This expects a POST request with a JSON body representing a [RequestUser].
|
||||||
|
class RegisterController extends Controller {
|
||||||
|
final ServerDatabase db;
|
||||||
|
|
||||||
|
final _crypt = PassCrypt();
|
||||||
|
final _rand = Random.secure();
|
||||||
|
|
||||||
|
RegisterController(this.db);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Response> handle(Request request) async {
|
||||||
|
final json = await request.body.decode<Map<String, dynamic>>();
|
||||||
|
final requestUser = RequestUser.fromJson(json);
|
||||||
|
|
||||||
|
// Check if we already have a user with that name.
|
||||||
|
final existingUser = await db.getUser(requestUser.name);
|
||||||
|
if (existingUser != null) {
|
||||||
|
// Returning something different than 200 here has the security
|
||||||
|
// implication that an attacker can check for existing user names. At the
|
||||||
|
// moment, I don't see any alternatives, because we don't use email
|
||||||
|
// addresses for identification. The client needs to know, whether the
|
||||||
|
// user name is already given.
|
||||||
|
return Response.conflict();
|
||||||
|
} else {
|
||||||
|
final bytes = List.generate(32, (i) => _rand.nextInt(256));
|
||||||
|
final salt = base64UrlEncode(bytes);
|
||||||
|
final hash = _crypt.hashPass(salt, requestUser.password);
|
||||||
|
|
||||||
|
db.updateUser(User(
|
||||||
|
name: requestUser.name,
|
||||||
|
email: requestUser.email,
|
||||||
|
salt: salt,
|
||||||
|
hash: hash,
|
||||||
|
mayUpload: true,
|
||||||
|
mayEdit: false,
|
||||||
|
mayDelete: false,
|
||||||
|
));
|
||||||
|
|
||||||
|
return Response.ok(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Endpoint controller for user login.
|
||||||
|
///
|
||||||
|
/// This expects a POST request with a JSON body representing a [RequestUser].
|
||||||
|
class LoginController extends Controller {
|
||||||
|
final ServerDatabase db;
|
||||||
|
|
||||||
|
/// The secret that will be used for signing the token.
|
||||||
|
final String secret;
|
||||||
|
|
||||||
|
final _crypt = PassCrypt();
|
||||||
|
final JWTHmacSha256Signer _signer;
|
||||||
|
|
||||||
|
LoginController(this.db, this.secret) : _signer = JWTHmacSha256Signer(secret);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Response> handle(Request request) async {
|
||||||
|
if (request.method == 'POST') {
|
||||||
|
final json = await request.body.decode<Map<String, dynamic>>();
|
||||||
|
final requestUser = RequestUser.fromJson(json);
|
||||||
|
|
||||||
|
final realUser = await db.getUser(requestUser.name);
|
||||||
|
if (realUser != null) {
|
||||||
|
if (_crypt.checkPassKey(
|
||||||
|
realUser.salt, requestUser.password, realUser.hash)) {
|
||||||
|
final builder = JWTBuilder()
|
||||||
|
..expiresAt = DateTime.now().add(Duration(minutes: 30))
|
||||||
|
..setClaim('user', requestUser.name);
|
||||||
|
|
||||||
|
final token = builder.getSignedToken(_signer).toString();
|
||||||
|
|
||||||
|
return Response.ok(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response.unauthorized();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response(HttpStatus.methodNotAllowed, null, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Middleware for checking authorization.
|
||||||
|
///
|
||||||
|
/// This will set the fields defined in [AuthorizationInfo] on this request
|
||||||
|
/// according to the provided access token.
|
||||||
|
class AuthorizationController extends Controller {
|
||||||
|
final ServerDatabase db;
|
||||||
|
|
||||||
|
/// The secret that was used to sign the token.
|
||||||
|
final String secret;
|
||||||
|
|
||||||
|
final JWTHmacSha256Signer _signer;
|
||||||
|
|
||||||
|
AuthorizationController(this.db, this.secret)
|
||||||
|
: _signer = JWTHmacSha256Signer(secret);
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<RequestOrResponse> handle(Request request) async {
|
||||||
|
final authHeaderValue =
|
||||||
|
request.raw.headers.value(HttpHeaders.authorizationHeader);
|
||||||
|
|
||||||
|
if (authHeaderValue != null) {
|
||||||
|
final authHeaderParts = authHeaderValue.split(' ');
|
||||||
|
|
||||||
|
if (authHeaderParts.length == 2 && authHeaderParts[0] == 'Bearer') {
|
||||||
|
final jwt = JWT.parse(authHeaderParts[1]);
|
||||||
|
|
||||||
|
if (jwt.verify(_signer)) {
|
||||||
|
final user = await db.getUser(jwt.claims['user']);
|
||||||
|
if (user != null) {
|
||||||
|
request.mayUpload = user.mayUpload;
|
||||||
|
request.mayEdit = user.mayEdit;
|
||||||
|
request.mayDelete = user.mayDelete;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,7 +7,11 @@ class MusicusServerConfiguration extends Configuration {
|
||||||
|
|
||||||
String host;
|
String host;
|
||||||
int port;
|
int port;
|
||||||
|
String secret;
|
||||||
|
|
||||||
@optionalConfiguration
|
@optionalConfiguration
|
||||||
String dbPath;
|
String dbPath;
|
||||||
|
|
||||||
|
@optionalConfiguration
|
||||||
|
String serverDbPath;
|
||||||
}
|
}
|
||||||
19
server/lib/src/database.dart
Normal file
19
server/lib/src/database.dart
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
import 'package:moor/moor.dart';
|
||||||
|
|
||||||
|
part 'database.g.dart';
|
||||||
|
|
||||||
|
@UseMoor(include: {'database.moor'})
|
||||||
|
class ServerDatabase extends _$ServerDatabase {
|
||||||
|
@override
|
||||||
|
final schemaVersion = 0;
|
||||||
|
|
||||||
|
ServerDatabase(QueryExecutor e) : super(e);
|
||||||
|
|
||||||
|
Future<User> getUser(String name) async {
|
||||||
|
return await (select(users)..where((u) => u.name.equals(name))).getSingle();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateUser(User user) async {
|
||||||
|
await into(users).insert(user, mode: InsertMode.insertOrReplace);
|
||||||
|
}
|
||||||
|
}
|
||||||
9
server/lib/src/database.moor
Normal file
9
server/lib/src/database.moor
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
CREATE TABLE users (
|
||||||
|
name TEXT PRIMARY KEY,
|
||||||
|
email TEXT,
|
||||||
|
salt TEXT NOT NULL,
|
||||||
|
hash TEXT NOT NULL,
|
||||||
|
may_upload BOOLEAN NOT NULL DEFAULT TRUE,
|
||||||
|
may_edit BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
may_delete BOOLEAN NOT NULL DEFAULT FALSE
|
||||||
|
);
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import 'package:aqueduct/aqueduct.dart';
|
import 'package:aqueduct/aqueduct.dart';
|
||||||
import 'package:musicus_database/musicus_database.dart';
|
import 'package:musicus_database/musicus_database.dart';
|
||||||
|
|
||||||
|
import 'auth.dart';
|
||||||
|
|
||||||
class EnsemblesController extends ResourceController {
|
class EnsemblesController extends ResourceController {
|
||||||
final Database db;
|
final Database db;
|
||||||
|
|
||||||
|
|
@ -26,6 +28,16 @@ class EnsemblesController extends ResourceController {
|
||||||
@Operation.put('id')
|
@Operation.put('id')
|
||||||
Future<Response> putEnsemble(
|
Future<Response> putEnsemble(
|
||||||
@Bind.path('id') int id, @Bind.body() Map<String, dynamic> json) async {
|
@Bind.path('id') int id, @Bind.body() Map<String, dynamic> json) async {
|
||||||
|
if (await db.ensembleById(id).getSingle() != null) {
|
||||||
|
if (!request.mayEdit) {
|
||||||
|
return Response.forbidden();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!request.mayUpload) {
|
||||||
|
return Response.forbidden();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final ensemble = Ensemble.fromJson(json).copyWith(
|
final ensemble = Ensemble.fromJson(json).copyWith(
|
||||||
id: id,
|
id: id,
|
||||||
);
|
);
|
||||||
|
|
@ -37,6 +49,10 @@ class EnsemblesController extends ResourceController {
|
||||||
|
|
||||||
@Operation.delete('id')
|
@Operation.delete('id')
|
||||||
Future<Response> deleteEnsemble(@Bind.path('id') int id) async {
|
Future<Response> deleteEnsemble(@Bind.path('id') int id) async {
|
||||||
|
if (!request.mayDelete) {
|
||||||
|
return Response.forbidden();
|
||||||
|
}
|
||||||
|
|
||||||
await db.deleteEnsemble(id);
|
await db.deleteEnsemble(id);
|
||||||
return Response.ok(null);
|
return Response.ok(null);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import 'package:aqueduct/aqueduct.dart';
|
import 'package:aqueduct/aqueduct.dart';
|
||||||
import 'package:musicus_database/musicus_database.dart';
|
import 'package:musicus_database/musicus_database.dart';
|
||||||
|
|
||||||
|
import 'auth.dart';
|
||||||
|
|
||||||
class InstrumentsController extends ResourceController {
|
class InstrumentsController extends ResourceController {
|
||||||
final Database db;
|
final Database db;
|
||||||
|
|
||||||
|
|
@ -26,6 +28,16 @@ class InstrumentsController extends ResourceController {
|
||||||
@Operation.put('id')
|
@Operation.put('id')
|
||||||
Future<Response> putInstrument(
|
Future<Response> putInstrument(
|
||||||
@Bind.path('id') int id, @Bind.body() Map<String, dynamic> json) async {
|
@Bind.path('id') int id, @Bind.body() Map<String, dynamic> json) async {
|
||||||
|
if (await db.instrumentById(id).getSingle() != null) {
|
||||||
|
if (!request.mayEdit) {
|
||||||
|
return Response.forbidden();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!request.mayUpload) {
|
||||||
|
return Response.forbidden();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final instrument = Instrument.fromJson(json).copyWith(
|
final instrument = Instrument.fromJson(json).copyWith(
|
||||||
id: id,
|
id: id,
|
||||||
);
|
);
|
||||||
|
|
@ -37,6 +49,10 @@ class InstrumentsController extends ResourceController {
|
||||||
|
|
||||||
@Operation.delete('id')
|
@Operation.delete('id')
|
||||||
Future<Response> deleteInstrument(@Bind.path('id') int id) async {
|
Future<Response> deleteInstrument(@Bind.path('id') int id) async {
|
||||||
|
if (!request.mayDelete) {
|
||||||
|
return Response.forbidden();
|
||||||
|
}
|
||||||
|
|
||||||
await db.deleteInstrument(id);
|
await db.deleteInstrument(id);
|
||||||
return Response.ok(null);
|
return Response.ok(null);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import 'package:aqueduct/aqueduct.dart';
|
import 'package:aqueduct/aqueduct.dart';
|
||||||
import 'package:musicus_database/musicus_database.dart';
|
import 'package:musicus_database/musicus_database.dart';
|
||||||
|
|
||||||
|
import 'auth.dart';
|
||||||
|
|
||||||
class PersonsController extends ResourceController {
|
class PersonsController extends ResourceController {
|
||||||
final Database db;
|
final Database db;
|
||||||
|
|
||||||
|
|
@ -26,6 +28,16 @@ class PersonsController extends ResourceController {
|
||||||
@Operation.put('id')
|
@Operation.put('id')
|
||||||
Future<Response> putPerson(
|
Future<Response> putPerson(
|
||||||
@Bind.path('id') int id, @Bind.body() Map<String, dynamic> json) async {
|
@Bind.path('id') int id, @Bind.body() Map<String, dynamic> json) async {
|
||||||
|
if (await db.personById(id).getSingle() != null) {
|
||||||
|
if (!request.mayEdit) {
|
||||||
|
return Response.forbidden();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!request.mayUpload) {
|
||||||
|
return Response.forbidden();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final person = Person.fromJson(json).copyWith(
|
final person = Person.fromJson(json).copyWith(
|
||||||
id: id,
|
id: id,
|
||||||
);
|
);
|
||||||
|
|
@ -37,6 +49,10 @@ class PersonsController extends ResourceController {
|
||||||
|
|
||||||
@Operation.delete('id')
|
@Operation.delete('id')
|
||||||
Future<Response> deletePerson(@Bind.path('id') int id) async {
|
Future<Response> deletePerson(@Bind.path('id') int id) async {
|
||||||
|
if (!request.mayDelete) {
|
||||||
|
return Response.forbidden();
|
||||||
|
}
|
||||||
|
|
||||||
await db.deletePerson(id);
|
await db.deletePerson(id);
|
||||||
return Response.ok(null);
|
return Response.ok(null);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import 'package:aqueduct/aqueduct.dart';
|
import 'package:aqueduct/aqueduct.dart';
|
||||||
import 'package:musicus_database/musicus_database.dart';
|
import 'package:musicus_database/musicus_database.dart';
|
||||||
|
|
||||||
|
import 'auth.dart';
|
||||||
|
|
||||||
class RecordingsController extends ResourceController {
|
class RecordingsController extends ResourceController {
|
||||||
final Database db;
|
final Database db;
|
||||||
|
|
||||||
|
|
@ -19,6 +21,16 @@ class RecordingsController extends ResourceController {
|
||||||
@Operation.put('id')
|
@Operation.put('id')
|
||||||
Future<Response> putRecording(
|
Future<Response> putRecording(
|
||||||
@Bind.path('id') int id, @Bind.body() Map<String, dynamic> json) async {
|
@Bind.path('id') int id, @Bind.body() Map<String, dynamic> json) async {
|
||||||
|
if (await db.recordingById(id).getSingle() != null) {
|
||||||
|
if (!request.mayEdit) {
|
||||||
|
return Response.forbidden();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!request.mayUpload) {
|
||||||
|
return Response.forbidden();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final recordingInfo = RecordingInfo.fromJson(json);
|
final recordingInfo = RecordingInfo.fromJson(json);
|
||||||
await db.updateRecording(recordingInfo);
|
await db.updateRecording(recordingInfo);
|
||||||
|
|
||||||
|
|
@ -27,6 +39,10 @@ class RecordingsController extends ResourceController {
|
||||||
|
|
||||||
@Operation.delete('id')
|
@Operation.delete('id')
|
||||||
Future<Response> deleteRecording(@Bind.path('id') int id) async {
|
Future<Response> deleteRecording(@Bind.path('id') int id) async {
|
||||||
|
if (!request.mayDelete) {
|
||||||
|
return Response.forbidden();
|
||||||
|
}
|
||||||
|
|
||||||
await db.deleteRecording(id);
|
await db.deleteRecording(id);
|
||||||
return Response.ok(null);
|
return Response.ok(null);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,18 +3,22 @@ import 'dart:io';
|
||||||
import 'package:aqueduct/aqueduct.dart';
|
import 'package:aqueduct/aqueduct.dart';
|
||||||
import 'package:moor_ffi/moor_ffi.dart';
|
import 'package:moor_ffi/moor_ffi.dart';
|
||||||
import 'package:musicus_database/musicus_database.dart';
|
import 'package:musicus_database/musicus_database.dart';
|
||||||
import 'package:musicus_server/src/work_recordings.dart';
|
|
||||||
|
|
||||||
|
import 'auth.dart';
|
||||||
import 'compositions.dart';
|
import 'compositions.dart';
|
||||||
import 'configuration.dart';
|
import 'configuration.dart';
|
||||||
|
import 'database.dart';
|
||||||
import 'ensembles.dart';
|
import 'ensembles.dart';
|
||||||
import 'instruments.dart';
|
import 'instruments.dart';
|
||||||
import 'persons.dart';
|
import 'persons.dart';
|
||||||
import 'recordings.dart';
|
import 'recordings.dart';
|
||||||
import 'works.dart';
|
import 'works.dart';
|
||||||
|
import 'work_recordings.dart';
|
||||||
|
|
||||||
class MusicusServer extends ApplicationChannel {
|
class MusicusServer extends ApplicationChannel {
|
||||||
Database db;
|
Database db;
|
||||||
|
ServerDatabase serverDb;
|
||||||
|
String secret;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> prepare() async {
|
Future<void> prepare() async {
|
||||||
|
|
@ -25,15 +29,35 @@ class MusicusServer extends ApplicationChannel {
|
||||||
} else {
|
} else {
|
||||||
db = Database(VmDatabase.memory());
|
db = Database(VmDatabase.memory());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config.serverDbPath != null) {
|
||||||
|
serverDb = ServerDatabase(VmDatabase(File(config.serverDbPath)));
|
||||||
|
} else {
|
||||||
|
serverDb = ServerDatabase(VmDatabase.memory());
|
||||||
|
}
|
||||||
|
|
||||||
|
secret = config.secret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Controller get entryPoint => Router()
|
Controller get entryPoint => Router()
|
||||||
..route('/persons/[:id]').link(() => PersonsController(db))
|
..route('/login').link(() => LoginController(serverDb, secret))
|
||||||
|
..route('/register').link(() => RegisterController(serverDb))
|
||||||
|
..route('/persons/[:id]')
|
||||||
|
.link(() => AuthorizationController(serverDb, secret))
|
||||||
|
.link(() => PersonsController(db))
|
||||||
..route('/persons/:id/works').link(() => CompositionsController(db))
|
..route('/persons/:id/works').link(() => CompositionsController(db))
|
||||||
..route('/instruments/[:id]').link(() => InstrumentsController(db))
|
..route('/instruments/[:id]')
|
||||||
..route('/works/:id').link(() => WorksController(db))
|
.link(() => AuthorizationController(serverDb, secret))
|
||||||
|
.link(() => InstrumentsController(db))
|
||||||
|
..route('/works/:id')
|
||||||
|
.link(() => AuthorizationController(serverDb, secret))
|
||||||
|
.link(() => WorksController(db))
|
||||||
..route('/works/:id/recordings').link(() => WorkRecordingsController(db))
|
..route('/works/:id/recordings').link(() => WorkRecordingsController(db))
|
||||||
..route('/ensembles/[:id]').link(() => EnsemblesController(db))
|
..route('/ensembles/[:id]')
|
||||||
..route('/recordings/:id').link(() => RecordingsController(db));
|
.link(() => AuthorizationController(serverDb, secret))
|
||||||
|
.link(() => EnsemblesController(db))
|
||||||
|
..route('/recordings/:id')
|
||||||
|
.link(() => AuthorizationController(serverDb, secret))
|
||||||
|
.link(() => RecordingsController(db));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import 'package:aqueduct/aqueduct.dart';
|
import 'package:aqueduct/aqueduct.dart';
|
||||||
import 'package:musicus_database/musicus_database.dart';
|
import 'package:musicus_database/musicus_database.dart';
|
||||||
|
|
||||||
|
import 'auth.dart';
|
||||||
|
|
||||||
class WorksController extends ResourceController {
|
class WorksController extends ResourceController {
|
||||||
final Database db;
|
final Database db;
|
||||||
|
|
||||||
|
|
@ -19,6 +21,16 @@ class WorksController extends ResourceController {
|
||||||
@Operation.put('id')
|
@Operation.put('id')
|
||||||
Future<Response> putWork(
|
Future<Response> putWork(
|
||||||
@Bind.path('id') int id, @Bind.body() Map<String, dynamic> json) async {
|
@Bind.path('id') int id, @Bind.body() Map<String, dynamic> json) async {
|
||||||
|
if (await db.workById(id).getSingle() != null) {
|
||||||
|
if (!request.mayEdit) {
|
||||||
|
return Response.forbidden();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!request.mayUpload) {
|
||||||
|
return Response.forbidden();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final workInfo = WorkInfo.fromJson(json);
|
final workInfo = WorkInfo.fromJson(json);
|
||||||
await db.updateWork(workInfo);
|
await db.updateWork(workInfo);
|
||||||
|
|
||||||
|
|
@ -27,6 +39,10 @@ class WorksController extends ResourceController {
|
||||||
|
|
||||||
@Operation.delete('id')
|
@Operation.delete('id')
|
||||||
Future<Response> deleteWork(@Bind.path('id') int id) async {
|
Future<Response> deleteWork(@Bind.path('id') int id) async {
|
||||||
|
if (!request.mayDelete) {
|
||||||
|
return Response.forbidden();
|
||||||
|
}
|
||||||
|
|
||||||
await db.deleteWork(id);
|
await db.deleteWork(id);
|
||||||
return Response.ok(null);
|
return Response.ok(null);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,17 @@ description: A server hosting a Musicus database.
|
||||||
version: 0.0.1
|
version: 0.0.1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.3.0 <3.0.0"
|
sdk: ">=2.6.0 <3.0.0"
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
aqueduct:
|
aqueduct:
|
||||||
moor_ffi:
|
corsac_jwt:
|
||||||
|
moor: ^3.0.2
|
||||||
|
moor_ffi: ^0.5.0
|
||||||
musicus_database:
|
musicus_database:
|
||||||
path: ../database
|
path: ../database
|
||||||
|
steel_crypt:
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
build_runner:
|
||||||
|
moor_generator: ^3.0.0
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue