Move external secrets behind auth broker

This commit is contained in:
Codex
2026-07-20 14:19:55 +09:00
parent ef0d53aa87
commit cb36612b88
14 changed files with 55 additions and 240 deletions
@@ -7,8 +7,6 @@ class AppEnvironment {
required this.organizationApiBaseUrl,
required this.orgContextApiBaseUrl,
required this.orgContextTenantSlug,
required this.baronKeyId,
required this.baronKeySecret,
required this.appVersion,
required this.buildTimestamp,
});
@@ -43,15 +41,6 @@ class AppEnvironment {
'TDC114_ORG_CONTEXT_TENANT_SLUG',
defaultValue: 'hanmac-family',
);
const baronKeyId = String.fromEnvironment(
'TDC114_BARON_KEY_ID',
defaultValue: '',
);
const baronKeySecret = String.fromEnvironment(
'TDC114_BARON_KEY_SECRET',
defaultValue: '',
);
return AppEnvironment(
ssoBaseUrl: ssoBaseUrl,
apiBaseUrl: sharedApiBaseUrl,
@@ -70,8 +59,6 @@ class AppEnvironment {
: organizationApiBaseOverride)
: orgContextApiBaseOverride,
orgContextTenantSlug: orgContextTenantSlug,
baronKeyId: baronKeyId,
baronKeySecret: baronKeySecret,
appVersion: const String.fromEnvironment(
'APP_VERSION',
defaultValue: '0.1.0',
@@ -90,8 +77,6 @@ class AppEnvironment {
final String organizationApiBaseUrl;
final String orgContextApiBaseUrl;
final String orgContextTenantSlug;
final String baronKeyId;
final String baronKeySecret;
final String appVersion;
final String buildTimestamp;
}
@@ -12,8 +12,6 @@ class AuthSessionStore {
static const _tokenKey = 'tdc114plus.auth.token';
static const _expiresAtKey = 'tdc114plus.auth.expiresAt';
static const _userKey = 'tdc114plus.auth.user';
static const _orgContextCredentialKey =
'tdc114plus.auth.orgContextCredential';
static const _pendingRefKey = 'tdc114plus.auth.pending.ref';
static const _pendingExpiresAtKey = 'tdc114plus.auth.pending.expiresAt';
static const _pendingResendAfterAtKey =
@@ -30,17 +28,8 @@ class AuthSessionStore {
response.expiresAt.toUtc().toIso8601String(),
);
await prefs.setString(_userKey, jsonEncode(response.user.toJson()));
final orgContextCredential = response.orgContextCredential;
if (orgContextCredential == null) {
await prefs.remove(_orgContextCredentialKey);
} else {
await prefs.setString(
_orgContextCredentialKey,
jsonEncode(orgContextCredential.toJson()),
);
}
debugPrint(
'AuthSessionStore.save token=${response.token} tokenLength=${response.token.length} expiresAt=${response.expiresAt.toUtc().toIso8601String()} orgContextCredential=${orgContextCredential != null}',
'AuthSessionStore.save token=${response.token} tokenLength=${response.token.length} expiresAt=${response.expiresAt.toUtc().toIso8601String()}',
);
}
@@ -113,7 +102,6 @@ class AuthSessionStore {
final token = prefs.getString(_tokenKey);
final expiresAtValue = prefs.getString(_expiresAtKey);
final userValue = prefs.getString(_userKey);
final orgContextCredentialValue = prefs.getString(_orgContextCredentialKey);
if (token == null || expiresAtValue == null || userValue == null) {
debugPrint(
'AuthSessionStore.load missing token=${token != null} expiresAt=${expiresAtValue != null} user=${userValue != null}',
@@ -124,12 +112,9 @@ class AuthSessionStore {
token: token,
expiresAt: DateTime.parse(expiresAtValue),
user: LoginUser.fromJson(jsonDecode(userValue) as Map<String, dynamic>),
orgContextCredential: _decodeOrgContextCredential(
orgContextCredentialValue,
),
);
debugPrint(
'AuthSessionStore.load token=${session.token} tokenLength=${session.token.length} expiresAt=${session.expiresAt.toUtc().toIso8601String()} expired=${session.isExpired} orgContextCredential=${session.orgContextCredential != null}',
'AuthSessionStore.load token=${session.token} tokenLength=${session.token.length} expiresAt=${session.expiresAt.toUtc().toIso8601String()} expired=${session.isExpired}',
);
return session;
}
@@ -139,22 +124,9 @@ class AuthSessionStore {
await prefs.remove(_tokenKey);
await prefs.remove(_expiresAtKey);
await prefs.remove(_userKey);
await prefs.remove(_orgContextCredentialKey);
await clearPendingLink();
debugPrint('AuthSessionStore.clear');
}
OrgContextCredential? _decodeOrgContextCredential(String? value) {
if (value == null || value.trim().isEmpty) {
return null;
}
try {
return OrgContextCredential.fromJsonOrNull(jsonDecode(value));
} catch (_) {
return null;
}
}
}
class StoredAuthSession {
@@ -162,13 +134,11 @@ class StoredAuthSession {
required this.token,
required this.expiresAt,
required this.user,
this.orgContextCredential,
});
final String token;
final DateTime expiresAt;
final LoginUser user;
final OrgContextCredential? orgContextCredential;
bool get isExpired => !expiresAt.isAfter(DateTime.now().toUtc());
}
@@ -101,14 +101,12 @@ class PhoneLoginResponse {
required this.token,
required this.expiresAt,
required this.user,
this.orgContextCredential,
});
final String status;
final String token;
final DateTime expiresAt;
final LoginUser user;
final OrgContextCredential? orgContextCredential;
factory PhoneLoginResponse.fromJson(Map<String, dynamic> json) {
return PhoneLoginResponse(
@@ -116,9 +114,6 @@ class PhoneLoginResponse {
token: json['token'] as String? ?? json['accessToken'] as String? ?? '',
expiresAt: DateTime.parse(json['expiresAt'] as String),
user: LoginUser.fromJson(json['user'] as Map<String, dynamic>? ?? {}),
orgContextCredential: OrgContextCredential.fromJsonOrNull(
json['orgContextCredential'] ?? json['org_context'],
),
);
}
@@ -128,83 +123,10 @@ class PhoneLoginResponse {
'token': token,
'expiresAt': expiresAt.toUtc().toIso8601String(),
'user': user.toJson(),
if (orgContextCredential != null)
'orgContextCredential': orgContextCredential!.toJson(),
};
}
}
class OrgContextCredential {
const OrgContextCredential({
required this.baseUrl,
required this.tenantSlug,
required this.keyId,
required this.keySecret,
this.expiresAt,
});
final String baseUrl;
final String tenantSlug;
final String keyId;
final String keySecret;
final DateTime? expiresAt;
bool get isUsable {
return baseUrl.trim().isNotEmpty &&
tenantSlug.trim().isNotEmpty &&
keyId.trim().isNotEmpty &&
keySecret.trim().isNotEmpty &&
(expiresAt == null || expiresAt!.isAfter(DateTime.now().toUtc()));
}
factory OrgContextCredential.fromJson(Map<String, dynamic> json) {
return OrgContextCredential(
baseUrl: _readString(json, const ['baseUrl', 'base_url']),
tenantSlug: _readString(json, const ['tenantSlug', 'tenant_slug']),
keyId: _readString(json, const ['keyId', 'key_id']),
keySecret: _readString(json, const ['keySecret', 'key_secret']),
expiresAt: _readDate(json['expiresAt'] ?? json['expires_at']),
);
}
static OrgContextCredential? fromJsonOrNull(Object? value) {
if (value is! Map) {
return null;
}
final credential = OrgContextCredential.fromJson(
Map<String, dynamic>.from(value),
);
return credential.isUsable ? credential : null;
}
Map<String, dynamic> toJson() {
return {
'baseUrl': baseUrl,
'tenantSlug': tenantSlug,
'keyId': keyId,
'keySecret': keySecret,
if (expiresAt != null) 'expiresAt': expiresAt!.toUtc().toIso8601String(),
};
}
static String _readString(Map<String, dynamic> json, List<String> keys) {
for (final key in keys) {
final value = json[key];
if (value is String && value.trim().isNotEmpty) {
return value.trim();
}
}
return '';
}
static DateTime? _readDate(Object? value) {
if (value is! String || value.trim().isEmpty) {
return null;
}
return DateTime.tryParse(value)?.toUtc();
}
}
class PhoneLoginLinkInitRequest {
const PhoneLoginLinkInitRequest({
required this.phoneNumber,
@@ -16,8 +16,6 @@ class OrgContextApiClient {
const OrgContextApiClient({
required this.httpClient,
required this.baseUri,
required this.keyId,
required this.keySecret,
required this.tenantSlug,
this.sessionStore,
this.timeout = const Duration(seconds: 15),
@@ -28,8 +26,6 @@ class OrgContextApiClient {
final http.Client httpClient;
final Uri baseUri;
final String keyId;
final String keySecret;
final String tenantSlug;
final AuthSessionStore? sessionStore;
final Duration timeout;
@@ -91,27 +87,13 @@ class OrgContextApiClient {
String? tenantSlug,
}) async {
final session = await _session();
final sessionCredential = session?.orgContextCredential;
final requestedTenantSlug = tenantSlug?.trim();
if (sessionCredential != null && sessionCredential.isUsable) {
return _OrgContextCredentialSnapshot(
baseUri: Uri.parse(sessionCredential.baseUrl),
tenantSlug: requestedTenantSlug == null || requestedTenantSlug.isEmpty
? sessionCredential.tenantSlug
: requestedTenantSlug,
keyId: sessionCredential.keyId,
keySecret: sessionCredential.keySecret,
appSessionToken: session?.token ?? '',
);
}
return _OrgContextCredentialSnapshot(
baseUri: baseUri,
tenantSlug: requestedTenantSlug == null || requestedTenantSlug.isEmpty
? this.tenantSlug
: requestedTenantSlug,
keyId: keyId,
keySecret: keySecret,
appSessionToken: session?.token ?? '',
);
}
@@ -141,10 +123,6 @@ class OrgContextApiClient {
'accept': 'application/json',
if (appSessionToken.isNotEmpty) 'Authorization': 'Bearer $appSessionToken',
if (appSessionToken.isNotEmpty) 'X-App-Session': appSessionToken,
if (credential.keyId.trim().isNotEmpty)
'X-Baron-Key-ID': credential.keyId.trim(),
if (credential.keySecret.trim().isNotEmpty)
'X-Baron-Key-Secret': credential.keySecret.trim(),
};
debugPrint(
'OrgContextApiClient._headers headers=$headers tenantSlug=${credential.tenantSlug} base=${credential.baseUri}',
@@ -458,8 +436,6 @@ final orgContextApiClientProvider = Provider<OrgContextApiClient>((ref) {
return OrgContextApiClient(
httpClient: ref.watch(httpClientProvider),
baseUri: Uri.parse(environment.orgContextApiBaseUrl),
keyId: environment.baronKeyId,
keySecret: environment.baronKeySecret,
tenantSlug: environment.orgContextTenantSlug,
sessionStore: ref.watch(authSessionStoreProvider),
);
@@ -475,22 +451,18 @@ class _OrgContextCredentialSnapshot {
const _OrgContextCredentialSnapshot({
required this.baseUri,
required this.tenantSlug,
required this.keyId,
required this.keySecret,
required this.appSessionToken,
});
final Uri baseUri;
final String tenantSlug;
final String keyId;
final String keySecret;
final String appSessionToken;
String get cacheKey {
return [
baseUri.toString(),
tenantSlug,
appSessionToken.isEmpty ? keyId : appSessionToken,
appSessionToken,
].join('|');
}
}