feat: implement some api; add md editor to app; login and signup logic.

This commit is contained in:
2021-12-30 20:04:00 +08:00
parent 1a0e8f8de7
commit 853132f1a8
61 changed files with 3205 additions and 149 deletions
+16
View File
@@ -0,0 +1,16 @@
import 'package:angel3_auth/angel3_auth.dart';
import 'package:angel3_framework/angel3_framework.dart';
import 'package:angel3_orm/angel3_orm.dart' as orm;
import 'package:dde_gesture_manager_api/models.dart';
Future<void> configureServer(Angel app) async {
var auth = AngelAuth<User>(
jwtKey: app.configuration['jwt_secret'],
allowCookie: false,
deserializer: (p) async => (UserQuery()..where!.id.equals(int.parse(p)))
.getOne(app.container!.make<orm.QueryExecutor>())
.then((value) => value.value),
serializer: (p) => p.id ?? '',
);
await auth.configureServer(app);
}
+4
View File
@@ -1,8 +1,12 @@
import 'dart:async';
import 'package:angel3_framework/angel3_framework.dart';
import 'orm.dart' as orm;
import 'jwt.dart' as jwt;
import 'redis_cache.dart' as redis_cache;
Future configureServer(Angel app) async {
// Include any plugins you have made here.
await app.configure(orm.configureServer);
await app.configure(jwt.configureServer);
await app.configure(redis_cache.configureServer);
}
@@ -0,0 +1,36 @@
import 'dart:convert';
import 'package:angel3_framework/angel3_framework.dart';
import 'package:logging/logging.dart';
import 'package:neat_cache/neat_cache.dart';
Future<void> configureServer(Angel app) async {
final _log = Logger('RedisPlugin');
if (app.container == null) {
_log.severe('Angel3 container is null');
throw StateError('Angel.container is null. All authentication will fail.');
}
var appContainer = app.container!;
final cache = RedisCache(app.configuration);
appContainer.registerSingleton(cache);
}
class RedisCache {
late Cache cache;
RedisCache(Map config) {
var redisConfig = config['redis'] as Map? ?? {};
final cacheProvider = Cache.redisCacheProvider(
Uri(
scheme: 'redis',
host: redisConfig['host'],
port: redisConfig['port'],
userInfo: redisConfig['password'],
),
commandTimeLimit: const Duration(seconds: 1),
);
cache = Cache(cacheProvider).withCodec(utf8);
}
}
+9
View File
@@ -0,0 +1,9 @@
import 'package:angel3_serialize/angel3_serialize.dart';
part 'login_success.g.dart';
@serializable
class _LoginSuccess {
@SerializableField(isNullable: false)
String? token;
}
+9 -4
View File
@@ -1,19 +1,24 @@
import 'dart:convert';
import 'package:angel3_migration/angel3_migration.dart';
import 'package:angel3_serialize/angel3_serialize.dart';
import 'package:angel3_orm/angel3_orm.dart';
import 'package:dde_gesture_manager_api/src/models/base_model.dart';
import 'package:optional/optional.dart';
import 'package:crypto/crypto.dart';
part 'user.g.dart';
@serializable
@orm
abstract class _User extends BaseModel {
@Column(isNullable: false, indexType: IndexType.unique)
@SerializableField(isNullable: false)
String? get email;
@SerializableField(isNullable: false)
@Column(isNullable: false, length: 32)
@SerializableField(isNullable: true, exclude: true)
String? get password;
@SerializableField(isNullable: false)
String? get token;
}
String secret(String salt) => base64.encode(Hmac(sha256, salt.codeUnits).convert((password ?? '').codeUnits).bytes);
}
@@ -0,0 +1,78 @@
import 'dart:async';
import 'dart:convert';
import 'package:angel3_auth/angel3_auth.dart';
import 'package:angel3_framework/angel3_framework.dart';
import 'package:dde_gesture_manager_api/apis.dart';
import 'package:dde_gesture_manager_api/models.dart';
import 'package:mailer/mailer.dart';
import 'package:mailer/smtp_server.dart';
import 'package:uuid/uuid.dart';
import 'controller_extensions.dart';
Future configureServer(Angel app) async {
app.post(Apis.auth.loginOrSignup, (req, res) async {
var userParams = UserSerializer.fromMap(req.bodyAsMap);
userParams.password = req.bodyAsMap[UserFields.password];
var userQuery = UserQuery();
userQuery.where?.email.equals(userParams.email ?? '');
var user = await userQuery.getOne(req.queryExecutor);
if (user.isEmpty) {
String accessKey = Uuid().v1();
await req.cache
.withPrefix('sign_up:')[accessKey]
.set(json.encode({'email': userParams.email, 'password': userParams.password}), Duration(minutes: 30));
var smtpConfig = app.configuration['smtp'];
var smtpServer =
SmtpServer(smtpConfig['host'], ssl: true, username: smtpConfig['username'], password: smtpConfig['password']);
var message = Message()
..from = Address(smtpConfig['username'])
..recipients.add(userParams.email)
..subject = '确认注册'
..html = await app.viewGenerator!(
'confirm_sign_up.html',
{
"confirm_url": Uri(
scheme: Apis.apiScheme,
host: Apis.apiHost,
port: Apis.apiPort,
path: Apis.auth.confirmSignup(accessKey: accessKey.param),
),
},
);
send(message, smtpServer);
return res.notFound();
} else if (user.value.password != userParams.password) {
return res.unauthorized();
} else {
var angelAuth = req.container!.make<AngelAuth>();
await angelAuth.loginById(user.value.id!, req, res);
var authToken = req.container!.make<AuthToken>();
authToken.payload[UserFields.password] = user.value.secret(app.configuration['password_salt']);
var serializedToken = authToken.serialize(angelAuth.hmac);
return res.json(LoginSuccess(token: serializedToken));
}
});
app.get(Apis.auth.confirmSignup.route, (req, res) async {
var accessKey = req.params['accessKey'];
var cache = req.cache.withPrefix('sign_up:');
var signupInfo = await cache[accessKey].get();
if (signupInfo != null && signupInfo is String && signupInfo.isNotEmpty) {
var decodedSignupInfo = json.decode(signupInfo);
var userQuery = UserQuery();
userQuery.values.copyFrom(User(
email: decodedSignupInfo[UserFields.email],
password: decodedSignupInfo[UserFields.password],
));
await userQuery.insert(req.queryExecutor);
cache[accessKey].purge();
return res.render('sign_up_result.html', {'success': true});
}
return res.render('sign_up_result.html', {'success': false});
});
}
@@ -2,12 +2,24 @@ import 'dart:io';
import 'package:angel3_framework/angel3_framework.dart';
import 'package:angel3_orm/angel3_orm.dart' as orm;
import 'package:dde_gesture_manager_api/src/config/plugins/redis_cache.dart';
import 'package:neat_cache/neat_cache.dart';
extension ResponseNoContent on ResponseContext {
noContent() {
statusCode = HttpStatus.noContent;
return close();
}
notFound() {
statusCode = HttpStatus.notFound;
return close();
}
unauthorized() {
statusCode = HttpStatus.unauthorized;
return close();
}
}
extension QueryWhereId on orm.Query {
@@ -19,3 +31,7 @@ extension QueryWhereId on orm.Query {
extension QueryExecutor on RequestContext {
orm.QueryExecutor get queryExecutor => container!.make<orm.QueryExecutor>();
}
extension RedisExecutor on RequestContext {
Cache get cache => container!.make<RedisCache>().cache;
}
@@ -0,0 +1,35 @@
import 'package:angel3_auth/angel3_auth.dart';
import 'package:angel3_framework/angel3_framework.dart';
import 'package:dde_gesture_manager_api/models.dart';
RequestHandler jwtMiddleware() {
return (RequestContext req, ResponseContext res, {bool throwError = true}) async {
bool _reject(ResponseContext res) {
if (throwError) {
res.statusCode = 403;
throw AngelHttpException.forbidden();
} else {
return false;
}
}
if (req.container != null) {
var reqContainer = req.container!;
if (reqContainer.has<User>() || req.method == 'OPTIONS') {
return true;
} else if (reqContainer.has<Future<User>>()) {
User user = await reqContainer.makeAsync<User>();
var authToken = req.container!.make<AuthToken>();
if (user.secret(req.app!.configuration['password_salt']) != authToken.payload[UserFields.password]) {
return _reject(res);
}
return true;
} else {
return _reject(res);
}
} else {
return _reject(res);
}
};
}
@@ -1,16 +0,0 @@
import 'dart:async';
import 'package:angel3_framework/angel3_framework.dart';
import 'package:dde_gesture_manager_api/models.dart';
import 'controller_extensions.dart';
Future configureServer(Angel app) async {
app.get(
'/user/int:id',
(req, res) async {
var user = await (UserQuery()..where?.metadata.contains({"uid": req.params[UserFields.id]}))
.getOne(req.queryExecutor);
return res.json(user.value);
},
);
}
+10 -2
View File
@@ -1,6 +1,6 @@
import 'package:angel3_framework/angel3_framework.dart';
import 'package:file/file.dart';
import 'controllers/user_controllers.dart' as user_controllers;
import 'controllers/auth_controllers.dart' as auth_controllers;
import 'controllers/system_controllers.dart' as system_controllers;
/// Put your app routes here!
@@ -11,9 +11,17 @@ import 'controllers/system_controllers.dart' as system_controllers;
AngelConfigurer configureServer(FileSystem fileSystem) {
return (Angel app) async {
// ParseBody middleware
app.fallback((req, res) async {
if (req.method == "POST") {
await req.parseBody();
}
return true;
});
// Typically, you want to mount controllers first, after any global middleware.
await app.configure(system_controllers.configureServerWithFileSystem(fileSystem));
await app.configure(user_controllers.configureServer);
await app.configure(auth_controllers.configureServer);
// Throw a 404 if no route matched the request.
app.fallback((req, res) => throw AngelHttpException.notFound());