diff --git a/lib/src/models/user.dart b/lib/src/models/user.dart index 2008f63..4318c49 100644 --- a/lib/src/models/user.dart +++ b/lib/src/models/user.dart @@ -4,7 +4,7 @@ import 'package:angel_framework/common.dart'; class User extends Model { String email, username, password, salt; - final List roles = []; + List roles; User( {String id, @@ -12,8 +12,7 @@ class User extends Model { this.username, this.password, this.salt, - Iterable roles: const []}) { + this.roles: const []}) { this.id = id; - this.roles.addAll(roles ?? []); } } diff --git a/lib/src/routes/controllers/auth.dart b/lib/src/routes/controllers/auth.dart index 2221f30..cb7e281 100644 --- a/lib/src/routes/controllers/auth.dart +++ b/lib/src/routes/controllers/auth.dart @@ -7,11 +7,16 @@ import '../../services/user.dart'; class AuthController extends Controller { AngelAuth auth; - deserializer(String id) async => app.service('api/users').read(id); + /// Clients will see the result of `deserializer`, so let's pretend to be a client. + /// + /// Our User service is already wired to remove sensitive data from serialized JSON. + deserializer(String id) async => + app.service('api/users').read(id, {'provider': Providers.VIA_REST}); + serializer(User user) async => user.id; /// Attempt to log a user in - LocalAuthVerifier localVerifier(UserService userService) { + LocalAuthVerifier localVerifier(Service userService) { return (String username, String password) async { List users = await userService.index({ 'query': {'username': username} @@ -32,8 +37,8 @@ class AuthController extends Controller { auth = new AngelAuth(jwtKey: app.jwt_secret) ..serializer = serializer ..deserializer = deserializer - ..strategies.add(new LocalAuthStrategy( - localVerifier(app.container.make(UserService)))); + ..strategies + .add(new LocalAuthStrategy(localVerifier(app.service('api/users')))); await super.call(app); await app.configure(auth); @@ -41,9 +46,4 @@ class AuthController extends Controller { @Expose('/local', method: 'POST') login() => auth.authenticate('local'); - - @Expose('/register', method: 'POST') - register(RequestContext req, UserService userService) async { - // And your registration logic... - } } diff --git a/lib/src/services/user.dart b/lib/src/services/user.dart index 79d31fc..e66170b 100644 --- a/lib/src/services/user.dart +++ b/lib/src/services/user.dart @@ -1,4 +1,5 @@ import 'package:angel_common/angel_common.dart'; +import 'package:angel_framework/hooks.dart' as hooks; import 'package:crypto/crypto.dart' show sha256; import 'package:mongo_dart/mongo_dart.dart'; import 'package:random_string/random_string.dart' as rs; @@ -8,11 +9,22 @@ export '../models/user.dart'; configureServer(Db db) { return (Angel app) async { - app.use('/api/users', new UserService(db.collection('users'))); + app.use('/api/users', + new TypedService(new MongoService(db.collection('users')))); HookedService service = app.service('api/users'); - app.container.singleton(service.inner); + // Prevent clients from doing anything to the `users` service, + // apart from reading a single user's data. + service.before([ + HookedServiceEvent.INDEXED, + HookedServiceEvent.CREATED, + HookedServiceEvent.MODIFIED, + HookedServiceEvent.UPDATED, + HookedServiceEvent.REMOVED + ], hooks.disable()); + + // Validate new users, and also hash their passwords. service.beforeCreated ..listen(validateEvent(CREATE_USER)) ..listen((e) { @@ -22,37 +34,12 @@ configureServer(Db db) { hashPassword(e.data['password'], salt, app.jwt_secret) ..['salt'] = salt; }); + + // Remove sensitive data from serialized JSON. + service.afterAll(hooks.remove(['password', 'salt'])); }; } /// SHA-256 hash any string, particularly a password. String hashPassword(String password, String salt, String pepper) => sha256.convert(('$salt:$password:$pepper').codeUnits).toString(); - -/// Manages users. -/// -/// Here, we extended the base service class. This allows to only expose -/// specific methods, and also allows more freedom over things such as validation. -class UserService extends TypedService { - UserService(DbCollection collection) : super(new MongoService(collection)); - - @override - index([Map params]) { - if (params != null && params.containsKey('provider')) { - // Nobody needs to see the entire user list except for the server. - throw new AngelHttpException.forbidden(); - } - - return super.index(params); - } - - @override - create(data, [Map params]) { - if (params != null && params.containsKey('provider')) { - // Deny creating users to the public - this should be done by the server only. - throw new AngelHttpException.forbidden(); - } - - return super.create(data, params); - } -}