File size: 2,860 Bytes
14459e2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
import 'dart:io';
import 'dart:convert';

import 'package:scientry_api/auth/email_verification/api.dart';
import 'package:scientry_api/auth/login/api.dart';
import 'package:scientry_api/auth/register/api.dart';
import 'package:scientry_api/auth/reset_password/api.dart';
import 'package:scientry_api/service/executor_manager.dart';
import 'package:scientry_api/service/jwt_manager.dart';
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart';
import 'package:shelf_router/shelf_router.dart';

Middleware verifyJwtMiddleware({bool protectAll = true}) {
  return (Handler innerHandler) {
    return (Request request) async {
      if (!protectAll && _isNotProtected(request.url.path)) {
        return innerHandler(request);
      }

      final authHeader = request.headers['X-Scientry-Authorization'];
      if (authHeader == null || !authHeader.startsWith('Bearer ')) {
        return Response.forbidden(
          jsonEncode({
            "errorCode": -1,
            "errorMessage": "Missing or invalid Authorization header",
          }),
          headers: {'Content-Type': 'application/json'},
        );
      }

      final token = authHeader.substring(7);
      final jwt = JWTManager.verifyAccessToken(token);
      if (jwt == null) {
        return Response.forbidden(
          jsonEncode({
            "errorCode": -1,
            "errorMessage": "Invalid or expired token",
          }),
          headers: {'Content-Type': 'application/json'},
        );
      }
      final updatedRequest = request.change(context: {'claims': jwt.payload});
      return innerHandler(updatedRequest);
    };
  };
}

bool _isNotProtected(String path) {
  const nonProtectedPrefixes = ['api/auth', 'verify-email'];
  return nonProtectedPrefixes.any((prefix) => path.startsWith(prefix));
}

Future<void> main() async {
  final router = Router()
    ..post('/api/auth/register', RegisterHandler().register)
    ..post("/api/auth/login", LoginHandler().handler)
    ..post(
      "/api/auth/request-email-verification",
      EmailVerificationHandler().requestEmailVerification,
    )
    ..get('/verify-email', EmailVerificationHandler().verifyEmail)
    ..post(
      '/api/auth/request-reset-password',
      ResetPasswordHandler().sendResetPassCode,
    )
    ..post('/api/auth/reset-password', ResetPasswordHandler().resetPassword);

  final handler = Pipeline()
      .addMiddleware(logRequests())
      .addMiddleware(verifyJwtMiddleware(protectAll: false))
      .addHandler(router.call);

  final port = int.parse(Platform.environment['PORT'] ?? '7860');
  final server = await serve(handler, InternetAddress.anyIPv4, port);
  print('Server running at http://localhost:${server.port}');

  ProcessSignal.sigint.watch().listen((_) async {
    print('\nShutting down isolate pool...');
    ExecutorManager().shutdown();
    exit(0);
  });
}