fix: auth header generation

This commit is contained in:
Udhay-Adithya
2025-07-23 00:59:10 +05:30
parent bc779882f7
commit a29f594f00
6 changed files with 124 additions and 69 deletions

View File

@@ -1,13 +1,20 @@
import 'dart:async';
import 'dart:convert';
import 'dart:developer';
import 'dart:io';
import 'dart:math' as math;
import 'package:crypto/crypto.dart';
import '../../models/models.dart';
import '../../consts.dart';
/// Generates a simple OAuth 1.0a Authorization header directly from model
/// Generates a simple OAuth 1.0a Authorization header directly from AuthOAuth1Model model
///
/// This function supports two OAuth 1.0a signature methods:
/// - HMAC-SHA1: Most commonly used, requires consumer secret and optional token secret
/// - Plaintext: Simple concatenation, only use over HTTPS
///
/// The function automatically:
/// - Generates timestamp and nonce
/// - Creates the signature base string
/// - Signs using the specified method
/// - Formats the Authorization header
String generateOAuth1AuthHeader(
AuthOAuth1Model oauth1Model,
HttpRequestModel request,
@@ -19,7 +26,7 @@ String generateOAuth1AuthHeader(
// Build OAuth parameters map
final oauthParams = <String, String>{
'oauth_consumer_key': oauth1Model.consumerKey,
'oauth_signature_method': "HMAC-SHA1",
'oauth_signature_method': oauth1Model.signatureMethod.displayType,
'oauth_timestamp': timestamp,
'oauth_nonce': nonce,
'oauth_version': oauth1Model.version,
@@ -34,12 +41,13 @@ String generateOAuth1AuthHeader(
final method = request.method.name.toUpperCase();
final uri = Uri.parse(request.url);
final baseString = _createSignatureBaseString(method, uri, oauthParams);
final signingKey =
'${Uri.encodeComponent(oauth1Model.consumerSecret)}&'
'${Uri.encodeComponent(oauth1Model.tokenSecret ?? '')}';
// Generate signature using HMAC-SHA1
final signature = _generateHmacSha1Signature(baseString, signingKey);
// Generate signature based on signature method
final signature = _generateSignature(
oauth1Model.signatureMethod,
baseString,
oauth1Model,
);
oauthParams['oauth_signature'] = signature;
// Build Authorization header
@@ -50,18 +58,6 @@ String generateOAuth1AuthHeader(
return 'OAuth $authParts';
}
/// Helper function to clear saved OAuth 1.0 credentials
Future<void> clearOAuth1Credentials(File credentialsFile) async {
if (await credentialsFile.exists()) {
try {
await credentialsFile.delete();
log('Cleared OAuth 1.0 credentials');
} catch (e) {
log('Error clearing OAuth 1.0 credentials: $e');
}
}
}
/// Generates a random nonce for OAuth 1.0a
String _generateNonce() {
const chars =
@@ -75,47 +71,90 @@ String _generateNonce() {
);
}
/// Percent-encodes the [param] following RFC 5849.
///
/// All characters except uppercase and lowercase letters, digits and the
/// characters `-_.~` are percent-encoded.
///
/// See https://oauth.net/core/1.0a/#encoding_parameters.
String _encodeParam(String param) {
return Uri.encodeComponent(param)
.replaceAll('!', '%21')
.replaceAll('*', '%2A')
.replaceAll("'", '%27')
.replaceAll('(', '%28')
.replaceAll(')', '%29');
}
/// Creates the signature base string for OAuth 1.0a
String _createSignatureBaseString(
String method,
Uri uri,
Map<String, String> parameters,
) {
// Combine OAuth parameters with query parameters
final allParameters = <String, String>{...parameters};
// 1. Percent encode every key and value that will be signed
final Map<String, String> encodedParams = <String, String>{};
// Add query parameters from the URI
uri.queryParameters.forEach((key, value) {
allParameters[key] = value;
// Encode OAuth parameters
parameters.forEach((String k, String v) {
encodedParams[_encodeParam(k)] = _encodeParam(v);
});
// Sort parameters by key
final sortedKeys = allParameters.keys.toList()..sort();
// Add and encode query parameters from the URI
uri.queryParameters.forEach((String k, String v) {
encodedParams[_encodeParam(k)] = _encodeParam(v);
});
// Create parameter string
final paramString = sortedKeys
.map(
(key) =>
'${Uri.encodeComponent(key)}=${Uri.encodeComponent(allParameters[key]!)}',
)
// Remove 'realm' parameter if present (not included in signature)
encodedParams.remove('realm');
// 2. Sort the list of parameters alphabetically by encoded key
final List<String> sortedEncodedKeys = encodedParams.keys.toList()..sort();
// 3-7. Create parameter string
final String baseParams = sortedEncodedKeys
.map((String k) {
return '$k=${encodedParams[k]}';
})
.join('&');
// Create base URI (without query parameters)
final baseUri = uri.replace(queryParameters: {}).toString();
// Create base URI (origin + path)
final baseUri = uri.origin + uri.path;
// Create signature base string
return '${method.toUpperCase()}&'
'${Uri.encodeComponent(baseUri)}&'
'${Uri.encodeComponent(paramString)}';
'${Uri.encodeComponent(baseParams)}';
}
/// Generates signature based on the specified signature method
///
/// Supports three OAuth 1.0a signature methods:
/// - HMAC-SHA1: Creates HMAC signature using SHA-1 hash (recommended)
/// - Plaintext: Returns the signing key directly (use only over HTTPS)
String _generateSignature(
OAuth1SignatureMethod signatureMethod,
String baseString,
AuthOAuth1Model oauth1Model,
) {
switch (signatureMethod) {
case OAuth1SignatureMethod.hmacSha1:
final signingKey =
'${Uri.encodeComponent(oauth1Model.consumerSecret)}&'
'${Uri.encodeComponent(oauth1Model.tokenSecret ?? '')}';
return _generateHmacSha1Signature(baseString, signingKey);
case OAuth1SignatureMethod.plaintext:
final signingKey =
'${Uri.encodeComponent(oauth1Model.consumerSecret)}&'
'${Uri.encodeComponent(oauth1Model.tokenSecret ?? '')}';
return signingKey;
}
}
/// Generates HMAC-SHA1 signature for OAuth 1.0a
String _generateHmacSha1Signature(String baseString, String key) {
final keyBytes = utf8.encode(key);
final messageBytes = utf8.encode(baseString);
String _generateHmacSha1Signature(String text, String key) {
final hmac = Hmac(sha1, utf8.encode(key));
final digest = hmac.convert(utf8.encode(text)).bytes;
final hmac = Hmac(sha1, keyBytes);
final digest = hmac.convert(messageBytes);
return base64Encode(digest.bytes);
return base64Encode(digest);
}