logout method
Revokes the stored keyId; falls back to a prefix match against
listKeys when the file predates that field. Revoke is not
idempotent — a 400 (already revoked, unknown id, someone else's key)
from the revoke call itself is read as "already gone", not an error.
logout authenticates AS the key it is revoking, so when that exact
key is already dead (revoked from the phone, or the row is gone),
authMiddleware 401s every call with it — including the listKeys
fallback — before the request ever reaches the revoke logic that
produces the 400 above, with the message API key revoked. That 401
means the same thing the 400 does: nothing left to revoke.
A different 401 — Invalid API key (the hash matches no row at
all) or User not found — means this key does not resolve against
the environment logout is running against at all, most likely an
--env mismatch with the login that minted it. That is not "safe
to delete": the key may still be live elsewhere, and the local file
is its only record, so LogoutOutcome.unresolvedInEnv keeps it.
Implementation
Future<AuthLogoutResult> logout({
required Dio apiDio,
required Credentials creds,
}) async {
try {
final keyId =
creds.keyId ?? await _findKeyIdByPrefix(apiDio, creds.apiKey);
if (keyId == null) {
return const AuthLogoutResult(LogoutOutcome.alreadyGone);
}
await apiDio.delete('${Endpoints.apiKeys}/$keyId');
return const AuthLogoutResult(LogoutOutcome.revoked);
} on DioException catch (e) {
final status = e.response?.statusCode;
final data = e.response?.data;
final message = data is Map ? data['message'] as String? : null;
if (status == 400 || (status == 401 && message == 'API key revoked')) {
return const AuthLogoutResult(LogoutOutcome.alreadyGone);
}
if (status == 401) {
return AuthLogoutResult(LogoutOutcome.unresolvedInEnv, detail: message);
}
rethrow;
}
}