mirror of
https://github.com/foss42/apidash.git
synced 2025-12-02 02:39:19 +08:00
feat: enhance JWT authentication handling with new fields and JWT generation utility
This commit is contained in:
@@ -1,12 +1,19 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'auth_jwt_model.g.dart';
|
||||
part 'auth_jwt_model.freezed.dart';
|
||||
part 'auth_jwt_model.g.dart';
|
||||
|
||||
@freezed
|
||||
class AuthJwtModel with _$AuthJwtModel {
|
||||
const factory AuthJwtModel({
|
||||
required String jwt,
|
||||
required String secret,
|
||||
required String payload,
|
||||
required String addTokenTo,
|
||||
required String algorithm,
|
||||
required bool isSecretBase64Encoded,
|
||||
required String headerPrefix,
|
||||
required String queryParamKey,
|
||||
required String header,
|
||||
}) = _AuthJwtModel;
|
||||
|
||||
factory AuthJwtModel.fromJson(Map<String, dynamic> json) =>
|
||||
|
||||
@@ -20,7 +20,14 @@ AuthJwtModel _$AuthJwtModelFromJson(Map<String, dynamic> json) {
|
||||
|
||||
/// @nodoc
|
||||
mixin _$AuthJwtModel {
|
||||
String get jwt => throw _privateConstructorUsedError;
|
||||
String get secret => throw _privateConstructorUsedError;
|
||||
String get payload => throw _privateConstructorUsedError;
|
||||
String get addTokenTo => throw _privateConstructorUsedError;
|
||||
String get algorithm => throw _privateConstructorUsedError;
|
||||
bool get isSecretBase64Encoded => throw _privateConstructorUsedError;
|
||||
String get headerPrefix => throw _privateConstructorUsedError;
|
||||
String get queryParamKey => throw _privateConstructorUsedError;
|
||||
String get header => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this AuthJwtModel to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@@ -38,7 +45,15 @@ abstract class $AuthJwtModelCopyWith<$Res> {
|
||||
AuthJwtModel value, $Res Function(AuthJwtModel) then) =
|
||||
_$AuthJwtModelCopyWithImpl<$Res, AuthJwtModel>;
|
||||
@useResult
|
||||
$Res call({String jwt});
|
||||
$Res call(
|
||||
{String secret,
|
||||
String payload,
|
||||
String addTokenTo,
|
||||
String algorithm,
|
||||
bool isSecretBase64Encoded,
|
||||
String headerPrefix,
|
||||
String queryParamKey,
|
||||
String header});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -56,12 +71,47 @@ class _$AuthJwtModelCopyWithImpl<$Res, $Val extends AuthJwtModel>
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? jwt = null,
|
||||
Object? secret = null,
|
||||
Object? payload = null,
|
||||
Object? addTokenTo = null,
|
||||
Object? algorithm = null,
|
||||
Object? isSecretBase64Encoded = null,
|
||||
Object? headerPrefix = null,
|
||||
Object? queryParamKey = null,
|
||||
Object? header = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
jwt: null == jwt
|
||||
? _value.jwt
|
||||
: jwt // ignore: cast_nullable_to_non_nullable
|
||||
secret: null == secret
|
||||
? _value.secret
|
||||
: secret // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
payload: null == payload
|
||||
? _value.payload
|
||||
: payload // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
addTokenTo: null == addTokenTo
|
||||
? _value.addTokenTo
|
||||
: addTokenTo // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
algorithm: null == algorithm
|
||||
? _value.algorithm
|
||||
: algorithm // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
isSecretBase64Encoded: null == isSecretBase64Encoded
|
||||
? _value.isSecretBase64Encoded
|
||||
: isSecretBase64Encoded // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
headerPrefix: null == headerPrefix
|
||||
? _value.headerPrefix
|
||||
: headerPrefix // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
queryParamKey: null == queryParamKey
|
||||
? _value.queryParamKey
|
||||
: queryParamKey // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
header: null == header
|
||||
? _value.header
|
||||
: header // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
) as $Val);
|
||||
}
|
||||
@@ -75,7 +125,15 @@ abstract class _$$AuthJwtModelImplCopyWith<$Res>
|
||||
__$$AuthJwtModelImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({String jwt});
|
||||
$Res call(
|
||||
{String secret,
|
||||
String payload,
|
||||
String addTokenTo,
|
||||
String algorithm,
|
||||
bool isSecretBase64Encoded,
|
||||
String headerPrefix,
|
||||
String queryParamKey,
|
||||
String header});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -91,12 +149,47 @@ class __$$AuthJwtModelImplCopyWithImpl<$Res>
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? jwt = null,
|
||||
Object? secret = null,
|
||||
Object? payload = null,
|
||||
Object? addTokenTo = null,
|
||||
Object? algorithm = null,
|
||||
Object? isSecretBase64Encoded = null,
|
||||
Object? headerPrefix = null,
|
||||
Object? queryParamKey = null,
|
||||
Object? header = null,
|
||||
}) {
|
||||
return _then(_$AuthJwtModelImpl(
|
||||
jwt: null == jwt
|
||||
? _value.jwt
|
||||
: jwt // ignore: cast_nullable_to_non_nullable
|
||||
secret: null == secret
|
||||
? _value.secret
|
||||
: secret // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
payload: null == payload
|
||||
? _value.payload
|
||||
: payload // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
addTokenTo: null == addTokenTo
|
||||
? _value.addTokenTo
|
||||
: addTokenTo // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
algorithm: null == algorithm
|
||||
? _value.algorithm
|
||||
: algorithm // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
isSecretBase64Encoded: null == isSecretBase64Encoded
|
||||
? _value.isSecretBase64Encoded
|
||||
: isSecretBase64Encoded // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
headerPrefix: null == headerPrefix
|
||||
? _value.headerPrefix
|
||||
: headerPrefix // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
queryParamKey: null == queryParamKey
|
||||
? _value.queryParamKey
|
||||
: queryParamKey // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
header: null == header
|
||||
? _value.header
|
||||
: header // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
@@ -105,17 +198,39 @@ class __$$AuthJwtModelImplCopyWithImpl<$Res>
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$AuthJwtModelImpl implements _AuthJwtModel {
|
||||
const _$AuthJwtModelImpl({required this.jwt});
|
||||
const _$AuthJwtModelImpl(
|
||||
{required this.secret,
|
||||
required this.payload,
|
||||
required this.addTokenTo,
|
||||
required this.algorithm,
|
||||
required this.isSecretBase64Encoded,
|
||||
required this.headerPrefix,
|
||||
required this.queryParamKey,
|
||||
required this.header});
|
||||
|
||||
factory _$AuthJwtModelImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$AuthJwtModelImplFromJson(json);
|
||||
|
||||
@override
|
||||
final String jwt;
|
||||
final String secret;
|
||||
@override
|
||||
final String payload;
|
||||
@override
|
||||
final String addTokenTo;
|
||||
@override
|
||||
final String algorithm;
|
||||
@override
|
||||
final bool isSecretBase64Encoded;
|
||||
@override
|
||||
final String headerPrefix;
|
||||
@override
|
||||
final String queryParamKey;
|
||||
@override
|
||||
final String header;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'AuthJwtModel(jwt: $jwt)';
|
||||
return 'AuthJwtModel(secret: $secret, payload: $payload, addTokenTo: $addTokenTo, algorithm: $algorithm, isSecretBase64Encoded: $isSecretBase64Encoded, headerPrefix: $headerPrefix, queryParamKey: $queryParamKey, header: $header)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -123,12 +238,25 @@ class _$AuthJwtModelImpl implements _AuthJwtModel {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$AuthJwtModelImpl &&
|
||||
(identical(other.jwt, jwt) || other.jwt == jwt));
|
||||
(identical(other.secret, secret) || other.secret == secret) &&
|
||||
(identical(other.payload, payload) || other.payload == payload) &&
|
||||
(identical(other.addTokenTo, addTokenTo) ||
|
||||
other.addTokenTo == addTokenTo) &&
|
||||
(identical(other.algorithm, algorithm) ||
|
||||
other.algorithm == algorithm) &&
|
||||
(identical(other.isSecretBase64Encoded, isSecretBase64Encoded) ||
|
||||
other.isSecretBase64Encoded == isSecretBase64Encoded) &&
|
||||
(identical(other.headerPrefix, headerPrefix) ||
|
||||
other.headerPrefix == headerPrefix) &&
|
||||
(identical(other.queryParamKey, queryParamKey) ||
|
||||
other.queryParamKey == queryParamKey) &&
|
||||
(identical(other.header, header) || other.header == header));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, jwt);
|
||||
int get hashCode => Object.hash(runtimeType, secret, payload, addTokenTo,
|
||||
algorithm, isSecretBase64Encoded, headerPrefix, queryParamKey, header);
|
||||
|
||||
/// Create a copy of AuthJwtModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@@ -147,13 +275,35 @@ class _$AuthJwtModelImpl implements _AuthJwtModel {
|
||||
}
|
||||
|
||||
abstract class _AuthJwtModel implements AuthJwtModel {
|
||||
const factory _AuthJwtModel({required final String jwt}) = _$AuthJwtModelImpl;
|
||||
const factory _AuthJwtModel(
|
||||
{required final String secret,
|
||||
required final String payload,
|
||||
required final String addTokenTo,
|
||||
required final String algorithm,
|
||||
required final bool isSecretBase64Encoded,
|
||||
required final String headerPrefix,
|
||||
required final String queryParamKey,
|
||||
required final String header}) = _$AuthJwtModelImpl;
|
||||
|
||||
factory _AuthJwtModel.fromJson(Map<String, dynamic> json) =
|
||||
_$AuthJwtModelImpl.fromJson;
|
||||
|
||||
@override
|
||||
String get jwt;
|
||||
String get secret;
|
||||
@override
|
||||
String get payload;
|
||||
@override
|
||||
String get addTokenTo;
|
||||
@override
|
||||
String get algorithm;
|
||||
@override
|
||||
bool get isSecretBase64Encoded;
|
||||
@override
|
||||
String get headerPrefix;
|
||||
@override
|
||||
String get queryParamKey;
|
||||
@override
|
||||
String get header;
|
||||
|
||||
/// Create a copy of AuthJwtModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
|
||||
@@ -8,10 +8,24 @@ part of 'auth_jwt_model.dart';
|
||||
|
||||
_$AuthJwtModelImpl _$$AuthJwtModelImplFromJson(Map<String, dynamic> json) =>
|
||||
_$AuthJwtModelImpl(
|
||||
jwt: json['jwt'] as String,
|
||||
secret: json['secret'] as String,
|
||||
payload: json['payload'] as String,
|
||||
addTokenTo: json['addTokenTo'] as String,
|
||||
algorithm: json['algorithm'] as String,
|
||||
isSecretBase64Encoded: json['isSecretBase64Encoded'] as bool,
|
||||
headerPrefix: json['headerPrefix'] as String,
|
||||
queryParamKey: json['queryParamKey'] as String,
|
||||
header: json['header'] as String,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$AuthJwtModelImplToJson(_$AuthJwtModelImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'jwt': instance.jwt,
|
||||
'secret': instance.secret,
|
||||
'payload': instance.payload,
|
||||
'addTokenTo': instance.addTokenTo,
|
||||
'algorithm': instance.algorithm,
|
||||
'isSecretBase64Encoded': instance.isSecretBase64Encoded,
|
||||
'headerPrefix': instance.headerPrefix,
|
||||
'queryParamKey': instance.queryParamKey,
|
||||
'header': instance.header,
|
||||
};
|
||||
|
||||
95
packages/apidash_core/lib/utils/auth_utils.dart
Normal file
95
packages/apidash_core/lib/utils/auth_utils.dart
Normal file
@@ -0,0 +1,95 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
import 'package:apidash_core/models/auth/auth_jwt_model.dart';
|
||||
import 'package:crypto/crypto.dart';
|
||||
|
||||
String generateJWT(AuthJwtModel jwtAuth) {
|
||||
try {
|
||||
Map<String, dynamic> header;
|
||||
if (jwtAuth.header.isNotEmpty) {
|
||||
try {
|
||||
header = json.decode(jwtAuth.header) as Map<String, dynamic>;
|
||||
} catch (e) {
|
||||
header = {};
|
||||
}
|
||||
} else {
|
||||
header = {};
|
||||
}
|
||||
header['typ'] = header['typ'] ?? 'JWT';
|
||||
header['alg'] = jwtAuth.algorithm;
|
||||
Map<String, dynamic> payload;
|
||||
if (jwtAuth.payload.isNotEmpty) {
|
||||
try {
|
||||
payload = json.decode(jwtAuth.payload) as Map<String, dynamic>;
|
||||
} catch (e) {
|
||||
payload = {};
|
||||
}
|
||||
} else {
|
||||
payload = {};
|
||||
}
|
||||
if (!payload.containsKey('iat')) {
|
||||
payload['iat'] = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||
}
|
||||
|
||||
// Encode header and payload
|
||||
final encodedHeader = _base64UrlEncode(utf8.encode(json.encode(header)));
|
||||
final encodedPayload = _base64UrlEncode(utf8.encode(json.encode(payload)));
|
||||
|
||||
// Create signature
|
||||
final signature = _createSignature(
|
||||
'$encodedHeader.$encodedPayload',
|
||||
jwtAuth.secret,
|
||||
jwtAuth.algorithm,
|
||||
jwtAuth.isSecretBase64Encoded,
|
||||
);
|
||||
|
||||
return '$encodedHeader.$encodedPayload.$signature';
|
||||
} catch (e) {
|
||||
// Return a basic JWT if generation fails
|
||||
return 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
|
||||
}
|
||||
}
|
||||
|
||||
String _createSignature(
|
||||
String data, String secret, String algorithm, bool isSecretBase64Encoded) {
|
||||
try {
|
||||
Uint8List secretBytes;
|
||||
if (isSecretBase64Encoded) {
|
||||
secretBytes = base64.decode(secret);
|
||||
} else {
|
||||
secretBytes = utf8.encode(secret);
|
||||
}
|
||||
|
||||
final dataBytes = utf8.encode(data);
|
||||
|
||||
switch (algorithm) {
|
||||
case 'HS256':
|
||||
final hmac = Hmac(sha256, secretBytes);
|
||||
final digest = hmac.convert(dataBytes);
|
||||
return _base64UrlEncode(digest.bytes);
|
||||
|
||||
case 'HS384':
|
||||
final hmac = Hmac(sha384, secretBytes);
|
||||
final digest = hmac.convert(dataBytes);
|
||||
return _base64UrlEncode(digest.bytes);
|
||||
|
||||
case 'HS512':
|
||||
final hmac = Hmac(sha512, secretBytes);
|
||||
final digest = hmac.convert(dataBytes);
|
||||
return _base64UrlEncode(digest.bytes);
|
||||
|
||||
default:
|
||||
// Default to HS256
|
||||
final hmac = Hmac(sha256, secretBytes);
|
||||
final digest = hmac.convert(dataBytes);
|
||||
return _base64UrlEncode(digest.bytes);
|
||||
}
|
||||
} catch (e) {
|
||||
// Return placeholder signature if creation fails
|
||||
return _base64UrlEncode(utf8.encode('signature_generation_failed'));
|
||||
}
|
||||
}
|
||||
|
||||
String _base64UrlEncode(List<int> bytes) {
|
||||
return base64Url.encode(bytes).replaceAll('=', '');
|
||||
}
|
||||
@@ -2,9 +2,11 @@ import 'dart:convert';
|
||||
import 'package:apidash_core/consts.dart';
|
||||
import 'package:apidash_core/models/auth/api_auth_model.dart';
|
||||
import 'package:apidash_core/models/http_request_model.dart';
|
||||
import 'package:apidash_core/utils/auth_utils.dart';
|
||||
import 'package:seed/seed.dart';
|
||||
|
||||
HttpRequestModel handleAuth(HttpRequestModel httpRequestModel, ApiAuthModel? auth) {
|
||||
HttpRequestModel handleAuth(
|
||||
HttpRequestModel httpRequestModel, ApiAuthModel? auth) {
|
||||
if (auth == null || auth.type == APIAuthType.none) {
|
||||
return httpRequestModel;
|
||||
}
|
||||
@@ -41,9 +43,26 @@ HttpRequestModel handleAuth(HttpRequestModel httpRequestModel, ApiAuthModel? aut
|
||||
case APIAuthType.jwt:
|
||||
if (auth.jwt != null) {
|
||||
final jwtAuth = auth.jwt!;
|
||||
updatedHeaders.add(NameValueModel(
|
||||
name: 'Authorization', value: 'Bearer ${jwtAuth.jwt}'));
|
||||
updatedHeaderEnabledList.add(true);
|
||||
|
||||
// Generate JWT token
|
||||
final jwtToken = generateJWT(jwtAuth);
|
||||
|
||||
if (jwtAuth.addTokenTo == 'header') {
|
||||
// Add to request header with prefix
|
||||
final headerValue = jwtAuth.headerPrefix.isNotEmpty
|
||||
? '${jwtAuth.headerPrefix} $jwtToken'
|
||||
: jwtToken;
|
||||
updatedHeaders
|
||||
.add(NameValueModel(name: 'Authorization', value: headerValue));
|
||||
updatedHeaderEnabledList.add(true);
|
||||
} else if (jwtAuth.addTokenTo == 'query') {
|
||||
// Add to query parameters(if selected)
|
||||
final paramKey = jwtAuth.queryParamKey.isNotEmpty
|
||||
? jwtAuth.queryParamKey
|
||||
: 'token';
|
||||
updatedParams.add(NameValueModel(name: paramKey, value: jwtToken));
|
||||
updatedParamEnabledList.add(true);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user