Skip to content

Commit

Permalink
implement initial auth update
Browse files Browse the repository at this point in the history
  • Loading branch information
dhemasnurjaya committed Oct 21, 2024
1 parent 69cc7d5 commit 8211e7c
Show file tree
Hide file tree
Showing 10 changed files with 383 additions and 27 deletions.
22 changes: 22 additions & 0 deletions lib/core/auth/auth_model.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import 'package:freezed_annotation/freezed_annotation.dart';

part 'auth_model.freezed.dart';
part 'auth_model.g.dart';

/// Represents the authentication token for each request.
@freezed
class AuthModel with _$AuthModel {
/// Create a new [AuthModel].
const factory AuthModel({
/// An UUID string, unique per request.
required String token,

/// Unix time in seconds. It is the time when the token was issued.
/// Will expire after a time window.
required int issuedAt,
}) = _AuthModel;

/// Creates a new [AuthModel] from a JSON object.
factory AuthModel.fromJson(Map<String, dynamic> json) =>
_$AuthModelFromJson(json);
}
197 changes: 197 additions & 0 deletions lib/core/auth/auth_model.freezed.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark

part of 'auth_model.dart';

// **************************************************************************
// FreezedGenerator
// **************************************************************************

T _$identity<T>(T value) => value;

final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');

AuthModel _$AuthModelFromJson(Map<String, dynamic> json) {
return _AuthModel.fromJson(json);
}

/// @nodoc
mixin _$AuthModel {
/// An UUID string, unique per request.
String get token => throw _privateConstructorUsedError;

/// Unix time in milliseconds.
/// The time when the token was issued.
/// Will expire after a time window.
int get issuedAt => throw _privateConstructorUsedError;

/// Serializes this AuthModel to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;

/// Create a copy of AuthModel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$AuthModelCopyWith<AuthModel> get copyWith =>
throw _privateConstructorUsedError;
}

/// @nodoc
abstract class $AuthModelCopyWith<$Res> {
factory $AuthModelCopyWith(AuthModel value, $Res Function(AuthModel) then) =
_$AuthModelCopyWithImpl<$Res, AuthModel>;
@useResult
$Res call({String token, int issuedAt});
}

/// @nodoc
class _$AuthModelCopyWithImpl<$Res, $Val extends AuthModel>
implements $AuthModelCopyWith<$Res> {
_$AuthModelCopyWithImpl(this._value, this._then);

// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;

/// Create a copy of AuthModel
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? token = null,
Object? issuedAt = null,
}) {
return _then(_value.copyWith(
token: null == token
? _value.token
: token // ignore: cast_nullable_to_non_nullable
as String,
issuedAt: null == issuedAt
? _value.issuedAt
: issuedAt // ignore: cast_nullable_to_non_nullable
as int,
) as $Val);
}
}

/// @nodoc
abstract class _$$AuthModelImplCopyWith<$Res>
implements $AuthModelCopyWith<$Res> {
factory _$$AuthModelImplCopyWith(
_$AuthModelImpl value, $Res Function(_$AuthModelImpl) then) =
__$$AuthModelImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({String token, int issuedAt});
}

/// @nodoc
class __$$AuthModelImplCopyWithImpl<$Res>
extends _$AuthModelCopyWithImpl<$Res, _$AuthModelImpl>
implements _$$AuthModelImplCopyWith<$Res> {
__$$AuthModelImplCopyWithImpl(
_$AuthModelImpl _value, $Res Function(_$AuthModelImpl) _then)
: super(_value, _then);

/// Create a copy of AuthModel
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? token = null,
Object? issuedAt = null,
}) {
return _then(_$AuthModelImpl(
token: null == token
? _value.token
: token // ignore: cast_nullable_to_non_nullable
as String,
issuedAt: null == issuedAt
? _value.issuedAt
: issuedAt // ignore: cast_nullable_to_non_nullable
as int,
));
}
}

/// @nodoc
@JsonSerializable()
class _$AuthModelImpl implements _AuthModel {
const _$AuthModelImpl({required this.token, required this.issuedAt});

factory _$AuthModelImpl.fromJson(Map<String, dynamic> json) =>
_$$AuthModelImplFromJson(json);

/// An UUID string, unique per request.
@override
final String token;

/// Unix time in milliseconds.
/// The time when the token was issued.
/// Will expire after a time window.
@override
final int issuedAt;

@override
String toString() {
return 'AuthModel(token: $token, issuedAt: $issuedAt)';
}

@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$AuthModelImpl &&
(identical(other.token, token) || other.token == token) &&
(identical(other.issuedAt, issuedAt) ||
other.issuedAt == issuedAt));
}

@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, token, issuedAt);

/// Create a copy of AuthModel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$AuthModelImplCopyWith<_$AuthModelImpl> get copyWith =>
__$$AuthModelImplCopyWithImpl<_$AuthModelImpl>(this, _$identity);

@override
Map<String, dynamic> toJson() {
return _$$AuthModelImplToJson(
this,
);
}
}

abstract class _AuthModel implements AuthModel {
const factory _AuthModel(
{required final String token,
required final int issuedAt}) = _$AuthModelImpl;

factory _AuthModel.fromJson(Map<String, dynamic> json) =
_$AuthModelImpl.fromJson;

/// An UUID string, unique per request.
@override
String get token;

/// Unix time in milliseconds.
/// The time when the token was issued.
/// Will expire after a time window.
@override
int get issuedAt;

/// Create a copy of AuthModel
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$AuthModelImplCopyWith<_$AuthModelImpl> get copyWith =>
throw _privateConstructorUsedError;
}
19 changes: 19 additions & 0 deletions lib/core/auth/auth_model.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions lib/core/auth/simple_auth.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,7 @@ abstract class SimpleAuth {

/// Check if [decryptedToken] is valid.
bool isTokenValid(String decryptedToken);

/// Check if [issuedAt] is expired.
bool isTokenExpired(int issuedAt);
}
20 changes: 19 additions & 1 deletion lib/core/auth/simple_auth_rsa.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'dart:convert';

import 'package:encrypt/encrypt.dart';
import 'package:kuwot_api/core/auth/simple_auth.dart';
import 'package:kuwot_api/core/time.dart';
import 'package:kuwot_api/env.dart';
import 'package:pointycastle/asymmetric/api.dart';
import 'package:uuid/validation.dart';
Expand All @@ -11,9 +12,14 @@ import 'package:uuid/validation.dart';
/// decrypt it with private key and check if the UUID is valid.
class SimpleAuthRSA implements SimpleAuth {
/// Creates a new [SimpleAuthRSA] with the provided [env].
SimpleAuthRSA({required Env env}) : _env = env;
SimpleAuthRSA({
required Env env,
required Time time,
}) : _env = env,
_time = time;

final Env _env;
final Time _time;

@override
String? decryptToken(String token) {
Expand All @@ -38,4 +44,16 @@ class SimpleAuthRSA implements SimpleAuth {
// check if token is a valid UUID
return UuidValidation.isValidUUID(fromString: decryptedToken);
}

@override
bool isTokenExpired(int issuedAt) {
const tokenLifetimeSeconds = 300;
const allowedDriftSeconds = 10;

final now = _time.getUnixTimestamp();
final diff = now - issuedAt;

return diff < -allowedDriftSeconds ||
diff > (tokenLifetimeSeconds + allowedDriftSeconds);
}
}
13 changes: 13 additions & 0 deletions lib/core/time.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/// Humble class for time related operations.
abstract class Time {
/// Get unix timestamp in seconds.
int getUnixTimestamp();
}

/// Implementation of [Time] using [DateTime].
class TimeImpl implements Time {
@override
int getUnixTimestamp() {
return DateTime.now().millisecondsSinceEpoch ~/ 1000;
}
}
10 changes: 9 additions & 1 deletion lib/injection_container.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import 'package:kuwot_api/core/auth/simple_auth.dart';
import 'package:kuwot_api/core/auth/simple_auth_rsa.dart';
import 'package:kuwot_api/core/network/network.dart';
import 'package:kuwot_api/core/time.dart';
import 'package:kuwot_api/data/data_sources/local/quote_local_data_source.dart';
import 'package:kuwot_api/data/data_sources/remote/unsplash_remote_data_source.dart';
import 'package:kuwot_api/data/quote_database.dart';
Expand All @@ -24,6 +25,7 @@ class InjectionContainer {
static final InjectionContainer _instance = InjectionContainer._internal();

Env? _env;
Time? _time;
Network? _network;
SimpleAuth? _simpleAuth;
QuoteDb? _quoteDatabase;
Expand All @@ -34,11 +36,17 @@ class InjectionContainer {
/// [Env] instance, used for environment variables.
Env get env => _env ??= EnvImpl();

/// [Time] instance, used for time operations.
Time get time => _time ??= TimeImpl();

/// [Network] instance, used for network operations.
Network get network => _network ??= NetworkImpl();

/// [SimpleAuth] instance, used for authentication.
SimpleAuth get simpleAuth => _simpleAuth ??= SimpleAuthRSA(env: env);
SimpleAuth get simpleAuth => _simpleAuth ??= SimpleAuthRSA(
env: env,
time: time,
);

/// [QuoteDb] instance, interacts with the quote SQLite database.
QuoteDb get quoteDatabase => _quoteDatabase ??= QuoteDb();
Expand Down
12 changes: 10 additions & 2 deletions routes/_middleware.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import 'dart:convert';

import 'package:dart_frog/dart_frog.dart';
import 'package:dart_frog_auth/dart_frog_auth.dart';
import 'package:kuwot_api/core/auth/auth_model.dart';
import 'package:kuwot_api/core/auth/simple_auth.dart';

import 'package:shelf_cors_headers/shelf_cors_headers.dart' as shelf;
Expand Down Expand Up @@ -37,8 +40,13 @@ Handler _authCheck(Handler handler) {
return null;
}

final isTokenValid = authenticator.isTokenValid(decryptedToken);
return isTokenValid ? true : null;
final authJson = jsonDecode(decryptedToken) as Map<String, dynamic>;
final authModel = AuthModel.fromJson(authJson);

final isTokenValid = authenticator.isTokenValid(authModel.token);
final isTokenExpired = authenticator.isTokenExpired(authModel.issuedAt);

return isTokenValid && !isTokenExpired;
},
applies: (RequestContext context) async {
return appliedRoutes.any((e) => context.request.uri.path.startsWith(e));
Expand Down
Loading

0 comments on commit 8211e7c

Please sign in to comment.