Account settings auth devices

This commit is contained in:
LittleSheep 2025-06-07 22:56:25 +08:00
parent d832729278
commit 3dd7c8a5b2
9 changed files with 861 additions and 48 deletions

View File

@ -338,5 +338,20 @@
"confirm": "Confirm",
"authFactorAdditional": "One more step",
"authFactorHint": "Contact method hint",
"authFactorHintHelper": "You need provide a part of your contact method and we will send the verification code to that contact method if it matched our records"
"authFactorHintHelper": "You need provide a part of your contact method and we will send the verification code to that contact method if it matched our records",
"authSessions": "Active Sessions",
"authSessionsDescription": "See devices you currently logged in.",
"authSessionsCount": {
"one": "{} session",
"other": "{} sessions"
},
"authDeviceCurrent": "Current device",
"lastActiveAt": "Last active at {}",
"authDeviceLogout": "Logout",
"authDeviceLogoutHint": "Are you sure you want to logout this device? This will also disable the push notification to this device.",
"authDeviceEditLabel": "Edit Label",
"authDeviceLabelTitle": "Edit Device Label",
"authDeviceLabelHint": "Enter a name for this device",
"authDeviceSwipeEditHint": "Swipe left to edit label",
"authDeviceSwipeLogoutHint": "Swipe right to logout device"
}

View File

@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objectVersion = 77;
objects = {
/* Begin PBXBuildFile section */
@ -448,10 +448,14 @@
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
);
inputPaths = (
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
@ -487,10 +491,14 @@
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
inputPaths = (
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";

View File

@ -18,13 +18,18 @@ sealed class SnAuthChallenge with _$SnAuthChallenge {
required DateTime expiredAt,
required int stepRemain,
required int stepTotal,
required int failedAttempts,
required int platform,
required int type,
required List<String> blacklistFactors,
required List<String> audiences,
required List<String> scopes,
required List<dynamic> audiences,
required List<dynamic> scopes,
required String ipAddress,
required String userAgent,
required String? deviceId,
required String deviceId,
required String? nonce,
required String? location,
required String accountId,
required DateTime createdAt,
required DateTime updatedAt,
required DateTime? deletedAt,
@ -34,6 +39,25 @@ sealed class SnAuthChallenge with _$SnAuthChallenge {
_$SnAuthChallengeFromJson(json);
}
@freezed
sealed class SnAuthSession with _$SnAuthSession {
const factory SnAuthSession({
required String id,
required String? label,
required DateTime lastGrantedAt,
required DateTime expiredAt,
required String accountId,
required String challengeId,
required SnAuthChallenge challenge,
required DateTime createdAt,
required DateTime updatedAt,
required DateTime? deletedAt,
}) = _SnAuthSession;
factory SnAuthSession.fromJson(Map<String, dynamic> json) =>
_$SnAuthSessionFromJson(json);
}
@freezed
sealed class SnAuthFactor with _$SnAuthFactor {
const factory SnAuthFactor({
@ -51,3 +75,19 @@ sealed class SnAuthFactor with _$SnAuthFactor {
factory SnAuthFactor.fromJson(Map<String, dynamic> json) =>
_$SnAuthFactorFromJson(json);
}
@freezed
sealed class SnAuthDevice with _$SnAuthDevice {
const factory SnAuthDevice({
required dynamic label,
required String userAgent,
required String deviceId,
required int platform,
required List<SnAuthSession> sessions,
// Not from backend, used for UI
@Default(false) bool isCurrent,
}) = _SnAuthDevice;
factory SnAuthDevice.fromJson(Map<String, dynamic> json) =>
_$SnAuthDeviceFromJson(json);
}

View File

@ -149,7 +149,7 @@ as String,
/// @nodoc
mixin _$SnAuthChallenge {
String get id; DateTime get expiredAt; int get stepRemain; int get stepTotal; List<String> get blacklistFactors; List<String> get audiences; List<String> get scopes; String get ipAddress; String get userAgent; String? get deviceId; String? get nonce; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
String get id; DateTime get expiredAt; int get stepRemain; int get stepTotal; int get failedAttempts; int get platform; int get type; List<String> get blacklistFactors; List<dynamic> get audiences; List<dynamic> get scopes; String get ipAddress; String get userAgent; String get deviceId; String? get nonce; String? get location; String get accountId; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
/// Create a copy of SnAuthChallenge
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@ -162,16 +162,16 @@ $SnAuthChallengeCopyWith<SnAuthChallenge> get copyWith => _$SnAuthChallengeCopyW
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAuthChallenge&&(identical(other.id, id) || other.id == id)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(identical(other.stepRemain, stepRemain) || other.stepRemain == stepRemain)&&(identical(other.stepTotal, stepTotal) || other.stepTotal == stepTotal)&&const DeepCollectionEquality().equals(other.blacklistFactors, blacklistFactors)&&const DeepCollectionEquality().equals(other.audiences, audiences)&&const DeepCollectionEquality().equals(other.scopes, scopes)&&(identical(other.ipAddress, ipAddress) || other.ipAddress == ipAddress)&&(identical(other.userAgent, userAgent) || other.userAgent == userAgent)&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.nonce, nonce) || other.nonce == nonce)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAuthChallenge&&(identical(other.id, id) || other.id == id)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(identical(other.stepRemain, stepRemain) || other.stepRemain == stepRemain)&&(identical(other.stepTotal, stepTotal) || other.stepTotal == stepTotal)&&(identical(other.failedAttempts, failedAttempts) || other.failedAttempts == failedAttempts)&&(identical(other.platform, platform) || other.platform == platform)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other.blacklistFactors, blacklistFactors)&&const DeepCollectionEquality().equals(other.audiences, audiences)&&const DeepCollectionEquality().equals(other.scopes, scopes)&&(identical(other.ipAddress, ipAddress) || other.ipAddress == ipAddress)&&(identical(other.userAgent, userAgent) || other.userAgent == userAgent)&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.nonce, nonce) || other.nonce == nonce)&&(identical(other.location, location) || other.location == location)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,expiredAt,stepRemain,stepTotal,const DeepCollectionEquality().hash(blacklistFactors),const DeepCollectionEquality().hash(audiences),const DeepCollectionEquality().hash(scopes),ipAddress,userAgent,deviceId,nonce,createdAt,updatedAt,deletedAt);
int get hashCode => Object.hashAll([runtimeType,id,expiredAt,stepRemain,stepTotal,failedAttempts,platform,type,const DeepCollectionEquality().hash(blacklistFactors),const DeepCollectionEquality().hash(audiences),const DeepCollectionEquality().hash(scopes),ipAddress,userAgent,deviceId,nonce,location,accountId,createdAt,updatedAt,deletedAt]);
@override
String toString() {
return 'SnAuthChallenge(id: $id, expiredAt: $expiredAt, stepRemain: $stepRemain, stepTotal: $stepTotal, blacklistFactors: $blacklistFactors, audiences: $audiences, scopes: $scopes, ipAddress: $ipAddress, userAgent: $userAgent, deviceId: $deviceId, nonce: $nonce, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
return 'SnAuthChallenge(id: $id, expiredAt: $expiredAt, stepRemain: $stepRemain, stepTotal: $stepTotal, failedAttempts: $failedAttempts, platform: $platform, type: $type, blacklistFactors: $blacklistFactors, audiences: $audiences, scopes: $scopes, ipAddress: $ipAddress, userAgent: $userAgent, deviceId: $deviceId, nonce: $nonce, location: $location, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
}
@ -182,7 +182,7 @@ abstract mixin class $SnAuthChallengeCopyWith<$Res> {
factory $SnAuthChallengeCopyWith(SnAuthChallenge value, $Res Function(SnAuthChallenge) _then) = _$SnAuthChallengeCopyWithImpl;
@useResult
$Res call({
String id, DateTime expiredAt, int stepRemain, int stepTotal, List<String> blacklistFactors, List<String> audiences, List<String> scopes, String ipAddress, String userAgent, String? deviceId, String? nonce, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
String id, DateTime expiredAt, int stepRemain, int stepTotal, int failedAttempts, int platform, int type, List<String> blacklistFactors, List<dynamic> audiences, List<dynamic> scopes, String ipAddress, String userAgent, String deviceId, String? nonce, String? location, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
});
@ -199,20 +199,25 @@ class _$SnAuthChallengeCopyWithImpl<$Res>
/// Create a copy of SnAuthChallenge
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? expiredAt = null,Object? stepRemain = null,Object? stepTotal = null,Object? blacklistFactors = null,Object? audiences = null,Object? scopes = null,Object? ipAddress = null,Object? userAgent = null,Object? deviceId = freezed,Object? nonce = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? expiredAt = null,Object? stepRemain = null,Object? stepTotal = null,Object? failedAttempts = null,Object? platform = null,Object? type = null,Object? blacklistFactors = null,Object? audiences = null,Object? scopes = null,Object? ipAddress = null,Object? userAgent = null,Object? deviceId = null,Object? nonce = freezed,Object? location = freezed,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,expiredAt: null == expiredAt ? _self.expiredAt : expiredAt // ignore: cast_nullable_to_non_nullable
as DateTime,stepRemain: null == stepRemain ? _self.stepRemain : stepRemain // ignore: cast_nullable_to_non_nullable
as int,stepTotal: null == stepTotal ? _self.stepTotal : stepTotal // ignore: cast_nullable_to_non_nullable
as int,failedAttempts: null == failedAttempts ? _self.failedAttempts : failedAttempts // ignore: cast_nullable_to_non_nullable
as int,platform: null == platform ? _self.platform : platform // ignore: cast_nullable_to_non_nullable
as int,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as int,blacklistFactors: null == blacklistFactors ? _self.blacklistFactors : blacklistFactors // ignore: cast_nullable_to_non_nullable
as List<String>,audiences: null == audiences ? _self.audiences : audiences // ignore: cast_nullable_to_non_nullable
as List<String>,scopes: null == scopes ? _self.scopes : scopes // ignore: cast_nullable_to_non_nullable
as List<String>,ipAddress: null == ipAddress ? _self.ipAddress : ipAddress // ignore: cast_nullable_to_non_nullable
as List<dynamic>,scopes: null == scopes ? _self.scopes : scopes // ignore: cast_nullable_to_non_nullable
as List<dynamic>,ipAddress: null == ipAddress ? _self.ipAddress : ipAddress // ignore: cast_nullable_to_non_nullable
as String,userAgent: null == userAgent ? _self.userAgent : userAgent // ignore: cast_nullable_to_non_nullable
as String,deviceId: freezed == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable
as String?,nonce: freezed == nonce ? _self.nonce : nonce // ignore: cast_nullable_to_non_nullable
as String?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as String,deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable
as String,nonce: freezed == nonce ? _self.nonce : nonce // ignore: cast_nullable_to_non_nullable
as String?,location: freezed == location ? _self.location : location // ignore: cast_nullable_to_non_nullable
as String?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
@ -226,13 +231,16 @@ as DateTime?,
@JsonSerializable()
class _SnAuthChallenge implements SnAuthChallenge {
const _SnAuthChallenge({required this.id, required this.expiredAt, required this.stepRemain, required this.stepTotal, required final List<String> blacklistFactors, required final List<String> audiences, required final List<String> scopes, required this.ipAddress, required this.userAgent, required this.deviceId, required this.nonce, required this.createdAt, required this.updatedAt, required this.deletedAt}): _blacklistFactors = blacklistFactors,_audiences = audiences,_scopes = scopes;
const _SnAuthChallenge({required this.id, required this.expiredAt, required this.stepRemain, required this.stepTotal, required this.failedAttempts, required this.platform, required this.type, required final List<String> blacklistFactors, required final List<dynamic> audiences, required final List<dynamic> scopes, required this.ipAddress, required this.userAgent, required this.deviceId, required this.nonce, required this.location, required this.accountId, required this.createdAt, required this.updatedAt, required this.deletedAt}): _blacklistFactors = blacklistFactors,_audiences = audiences,_scopes = scopes;
factory _SnAuthChallenge.fromJson(Map<String, dynamic> json) => _$SnAuthChallengeFromJson(json);
@override final String id;
@override final DateTime expiredAt;
@override final int stepRemain;
@override final int stepTotal;
@override final int failedAttempts;
@override final int platform;
@override final int type;
final List<String> _blacklistFactors;
@override List<String> get blacklistFactors {
if (_blacklistFactors is EqualUnmodifiableListView) return _blacklistFactors;
@ -240,15 +248,15 @@ class _SnAuthChallenge implements SnAuthChallenge {
return EqualUnmodifiableListView(_blacklistFactors);
}
final List<String> _audiences;
@override List<String> get audiences {
final List<dynamic> _audiences;
@override List<dynamic> get audiences {
if (_audiences is EqualUnmodifiableListView) return _audiences;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_audiences);
}
final List<String> _scopes;
@override List<String> get scopes {
final List<dynamic> _scopes;
@override List<dynamic> get scopes {
if (_scopes is EqualUnmodifiableListView) return _scopes;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_scopes);
@ -256,8 +264,10 @@ class _SnAuthChallenge implements SnAuthChallenge {
@override final String ipAddress;
@override final String userAgent;
@override final String? deviceId;
@override final String deviceId;
@override final String? nonce;
@override final String? location;
@override final String accountId;
@override final DateTime createdAt;
@override final DateTime updatedAt;
@override final DateTime? deletedAt;
@ -275,16 +285,16 @@ Map<String, dynamic> toJson() {
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAuthChallenge&&(identical(other.id, id) || other.id == id)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(identical(other.stepRemain, stepRemain) || other.stepRemain == stepRemain)&&(identical(other.stepTotal, stepTotal) || other.stepTotal == stepTotal)&&const DeepCollectionEquality().equals(other._blacklistFactors, _blacklistFactors)&&const DeepCollectionEquality().equals(other._audiences, _audiences)&&const DeepCollectionEquality().equals(other._scopes, _scopes)&&(identical(other.ipAddress, ipAddress) || other.ipAddress == ipAddress)&&(identical(other.userAgent, userAgent) || other.userAgent == userAgent)&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.nonce, nonce) || other.nonce == nonce)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAuthChallenge&&(identical(other.id, id) || other.id == id)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(identical(other.stepRemain, stepRemain) || other.stepRemain == stepRemain)&&(identical(other.stepTotal, stepTotal) || other.stepTotal == stepTotal)&&(identical(other.failedAttempts, failedAttempts) || other.failedAttempts == failedAttempts)&&(identical(other.platform, platform) || other.platform == platform)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other._blacklistFactors, _blacklistFactors)&&const DeepCollectionEquality().equals(other._audiences, _audiences)&&const DeepCollectionEquality().equals(other._scopes, _scopes)&&(identical(other.ipAddress, ipAddress) || other.ipAddress == ipAddress)&&(identical(other.userAgent, userAgent) || other.userAgent == userAgent)&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.nonce, nonce) || other.nonce == nonce)&&(identical(other.location, location) || other.location == location)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,expiredAt,stepRemain,stepTotal,const DeepCollectionEquality().hash(_blacklistFactors),const DeepCollectionEquality().hash(_audiences),const DeepCollectionEquality().hash(_scopes),ipAddress,userAgent,deviceId,nonce,createdAt,updatedAt,deletedAt);
int get hashCode => Object.hashAll([runtimeType,id,expiredAt,stepRemain,stepTotal,failedAttempts,platform,type,const DeepCollectionEquality().hash(_blacklistFactors),const DeepCollectionEquality().hash(_audiences),const DeepCollectionEquality().hash(_scopes),ipAddress,userAgent,deviceId,nonce,location,accountId,createdAt,updatedAt,deletedAt]);
@override
String toString() {
return 'SnAuthChallenge(id: $id, expiredAt: $expiredAt, stepRemain: $stepRemain, stepTotal: $stepTotal, blacklistFactors: $blacklistFactors, audiences: $audiences, scopes: $scopes, ipAddress: $ipAddress, userAgent: $userAgent, deviceId: $deviceId, nonce: $nonce, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
return 'SnAuthChallenge(id: $id, expiredAt: $expiredAt, stepRemain: $stepRemain, stepTotal: $stepTotal, failedAttempts: $failedAttempts, platform: $platform, type: $type, blacklistFactors: $blacklistFactors, audiences: $audiences, scopes: $scopes, ipAddress: $ipAddress, userAgent: $userAgent, deviceId: $deviceId, nonce: $nonce, location: $location, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
}
@ -295,7 +305,7 @@ abstract mixin class _$SnAuthChallengeCopyWith<$Res> implements $SnAuthChallenge
factory _$SnAuthChallengeCopyWith(_SnAuthChallenge value, $Res Function(_SnAuthChallenge) _then) = __$SnAuthChallengeCopyWithImpl;
@override @useResult
$Res call({
String id, DateTime expiredAt, int stepRemain, int stepTotal, List<String> blacklistFactors, List<String> audiences, List<String> scopes, String ipAddress, String userAgent, String? deviceId, String? nonce, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
String id, DateTime expiredAt, int stepRemain, int stepTotal, int failedAttempts, int platform, int type, List<String> blacklistFactors, List<dynamic> audiences, List<dynamic> scopes, String ipAddress, String userAgent, String deviceId, String? nonce, String? location, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
});
@ -312,20 +322,25 @@ class __$SnAuthChallengeCopyWithImpl<$Res>
/// Create a copy of SnAuthChallenge
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? expiredAt = null,Object? stepRemain = null,Object? stepTotal = null,Object? blacklistFactors = null,Object? audiences = null,Object? scopes = null,Object? ipAddress = null,Object? userAgent = null,Object? deviceId = freezed,Object? nonce = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? expiredAt = null,Object? stepRemain = null,Object? stepTotal = null,Object? failedAttempts = null,Object? platform = null,Object? type = null,Object? blacklistFactors = null,Object? audiences = null,Object? scopes = null,Object? ipAddress = null,Object? userAgent = null,Object? deviceId = null,Object? nonce = freezed,Object? location = freezed,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_SnAuthChallenge(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,expiredAt: null == expiredAt ? _self.expiredAt : expiredAt // ignore: cast_nullable_to_non_nullable
as DateTime,stepRemain: null == stepRemain ? _self.stepRemain : stepRemain // ignore: cast_nullable_to_non_nullable
as int,stepTotal: null == stepTotal ? _self.stepTotal : stepTotal // ignore: cast_nullable_to_non_nullable
as int,failedAttempts: null == failedAttempts ? _self.failedAttempts : failedAttempts // ignore: cast_nullable_to_non_nullable
as int,platform: null == platform ? _self.platform : platform // ignore: cast_nullable_to_non_nullable
as int,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as int,blacklistFactors: null == blacklistFactors ? _self._blacklistFactors : blacklistFactors // ignore: cast_nullable_to_non_nullable
as List<String>,audiences: null == audiences ? _self._audiences : audiences // ignore: cast_nullable_to_non_nullable
as List<String>,scopes: null == scopes ? _self._scopes : scopes // ignore: cast_nullable_to_non_nullable
as List<String>,ipAddress: null == ipAddress ? _self.ipAddress : ipAddress // ignore: cast_nullable_to_non_nullable
as List<dynamic>,scopes: null == scopes ? _self._scopes : scopes // ignore: cast_nullable_to_non_nullable
as List<dynamic>,ipAddress: null == ipAddress ? _self.ipAddress : ipAddress // ignore: cast_nullable_to_non_nullable
as String,userAgent: null == userAgent ? _self.userAgent : userAgent // ignore: cast_nullable_to_non_nullable
as String,deviceId: freezed == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable
as String?,nonce: freezed == nonce ? _self.nonce : nonce // ignore: cast_nullable_to_non_nullable
as String?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as String,deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable
as String,nonce: freezed == nonce ? _self.nonce : nonce // ignore: cast_nullable_to_non_nullable
as String?,location: freezed == location ? _self.location : location // ignore: cast_nullable_to_non_nullable
as String?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
@ -336,6 +351,184 @@ as DateTime?,
}
/// @nodoc
mixin _$SnAuthSession {
String get id; String? get label; DateTime get lastGrantedAt; DateTime get expiredAt; String get accountId; String get challengeId; SnAuthChallenge get challenge; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
/// Create a copy of SnAuthSession
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$SnAuthSessionCopyWith<SnAuthSession> get copyWith => _$SnAuthSessionCopyWithImpl<SnAuthSession>(this as SnAuthSession, _$identity);
/// Serializes this SnAuthSession to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAuthSession&&(identical(other.id, id) || other.id == id)&&(identical(other.label, label) || other.label == label)&&(identical(other.lastGrantedAt, lastGrantedAt) || other.lastGrantedAt == lastGrantedAt)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.challengeId, challengeId) || other.challengeId == challengeId)&&(identical(other.challenge, challenge) || other.challenge == challenge)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,label,lastGrantedAt,expiredAt,accountId,challengeId,challenge,createdAt,updatedAt,deletedAt);
@override
String toString() {
return 'SnAuthSession(id: $id, label: $label, lastGrantedAt: $lastGrantedAt, expiredAt: $expiredAt, accountId: $accountId, challengeId: $challengeId, challenge: $challenge, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
}
}
/// @nodoc
abstract mixin class $SnAuthSessionCopyWith<$Res> {
factory $SnAuthSessionCopyWith(SnAuthSession value, $Res Function(SnAuthSession) _then) = _$SnAuthSessionCopyWithImpl;
@useResult
$Res call({
String id, String? label, DateTime lastGrantedAt, DateTime expiredAt, String accountId, String challengeId, SnAuthChallenge challenge, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
});
$SnAuthChallengeCopyWith<$Res> get challenge;
}
/// @nodoc
class _$SnAuthSessionCopyWithImpl<$Res>
implements $SnAuthSessionCopyWith<$Res> {
_$SnAuthSessionCopyWithImpl(this._self, this._then);
final SnAuthSession _self;
final $Res Function(SnAuthSession) _then;
/// Create a copy of SnAuthSession
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? label = freezed,Object? lastGrantedAt = null,Object? expiredAt = null,Object? accountId = null,Object? challengeId = null,Object? challenge = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,label: freezed == label ? _self.label : label // ignore: cast_nullable_to_non_nullable
as String?,lastGrantedAt: null == lastGrantedAt ? _self.lastGrantedAt : lastGrantedAt // ignore: cast_nullable_to_non_nullable
as DateTime,expiredAt: null == expiredAt ? _self.expiredAt : expiredAt // ignore: cast_nullable_to_non_nullable
as DateTime,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
as String,challengeId: null == challengeId ? _self.challengeId : challengeId // ignore: cast_nullable_to_non_nullable
as String,challenge: null == challenge ? _self.challenge : challenge // ignore: cast_nullable_to_non_nullable
as SnAuthChallenge,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
));
}
/// Create a copy of SnAuthSession
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnAuthChallengeCopyWith<$Res> get challenge {
return $SnAuthChallengeCopyWith<$Res>(_self.challenge, (value) {
return _then(_self.copyWith(challenge: value));
});
}
}
/// @nodoc
@JsonSerializable()
class _SnAuthSession implements SnAuthSession {
const _SnAuthSession({required this.id, required this.label, required this.lastGrantedAt, required this.expiredAt, required this.accountId, required this.challengeId, required this.challenge, required this.createdAt, required this.updatedAt, required this.deletedAt});
factory _SnAuthSession.fromJson(Map<String, dynamic> json) => _$SnAuthSessionFromJson(json);
@override final String id;
@override final String? label;
@override final DateTime lastGrantedAt;
@override final DateTime expiredAt;
@override final String accountId;
@override final String challengeId;
@override final SnAuthChallenge challenge;
@override final DateTime createdAt;
@override final DateTime updatedAt;
@override final DateTime? deletedAt;
/// Create a copy of SnAuthSession
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$SnAuthSessionCopyWith<_SnAuthSession> get copyWith => __$SnAuthSessionCopyWithImpl<_SnAuthSession>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$SnAuthSessionToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAuthSession&&(identical(other.id, id) || other.id == id)&&(identical(other.label, label) || other.label == label)&&(identical(other.lastGrantedAt, lastGrantedAt) || other.lastGrantedAt == lastGrantedAt)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.challengeId, challengeId) || other.challengeId == challengeId)&&(identical(other.challenge, challenge) || other.challenge == challenge)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,label,lastGrantedAt,expiredAt,accountId,challengeId,challenge,createdAt,updatedAt,deletedAt);
@override
String toString() {
return 'SnAuthSession(id: $id, label: $label, lastGrantedAt: $lastGrantedAt, expiredAt: $expiredAt, accountId: $accountId, challengeId: $challengeId, challenge: $challenge, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
}
}
/// @nodoc
abstract mixin class _$SnAuthSessionCopyWith<$Res> implements $SnAuthSessionCopyWith<$Res> {
factory _$SnAuthSessionCopyWith(_SnAuthSession value, $Res Function(_SnAuthSession) _then) = __$SnAuthSessionCopyWithImpl;
@override @useResult
$Res call({
String id, String? label, DateTime lastGrantedAt, DateTime expiredAt, String accountId, String challengeId, SnAuthChallenge challenge, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
});
@override $SnAuthChallengeCopyWith<$Res> get challenge;
}
/// @nodoc
class __$SnAuthSessionCopyWithImpl<$Res>
implements _$SnAuthSessionCopyWith<$Res> {
__$SnAuthSessionCopyWithImpl(this._self, this._then);
final _SnAuthSession _self;
final $Res Function(_SnAuthSession) _then;
/// Create a copy of SnAuthSession
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? label = freezed,Object? lastGrantedAt = null,Object? expiredAt = null,Object? accountId = null,Object? challengeId = null,Object? challenge = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_SnAuthSession(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,label: freezed == label ? _self.label : label // ignore: cast_nullable_to_non_nullable
as String?,lastGrantedAt: null == lastGrantedAt ? _self.lastGrantedAt : lastGrantedAt // ignore: cast_nullable_to_non_nullable
as DateTime,expiredAt: null == expiredAt ? _self.expiredAt : expiredAt // ignore: cast_nullable_to_non_nullable
as DateTime,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
as String,challengeId: null == challengeId ? _self.challengeId : challengeId // ignore: cast_nullable_to_non_nullable
as String,challenge: null == challenge ? _self.challenge : challenge // ignore: cast_nullable_to_non_nullable
as SnAuthChallenge,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
));
}
/// Create a copy of SnAuthSession
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnAuthChallengeCopyWith<$Res> get challenge {
return $SnAuthChallengeCopyWith<$Res>(_self.challenge, (value) {
return _then(_self.copyWith(challenge: value));
});
}
}
/// @nodoc
mixin _$SnAuthFactor {
@ -498,6 +691,162 @@ as Map<String, dynamic>?,
}
}
/// @nodoc
mixin _$SnAuthDevice {
dynamic get label; String get userAgent; String get deviceId; int get platform; List<SnAuthSession> get sessions;// Not from backend, used for UI
bool get isCurrent;
/// Create a copy of SnAuthDevice
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$SnAuthDeviceCopyWith<SnAuthDevice> get copyWith => _$SnAuthDeviceCopyWithImpl<SnAuthDevice>(this as SnAuthDevice, _$identity);
/// Serializes this SnAuthDevice to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAuthDevice&&const DeepCollectionEquality().equals(other.label, label)&&(identical(other.userAgent, userAgent) || other.userAgent == userAgent)&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.platform, platform) || other.platform == platform)&&const DeepCollectionEquality().equals(other.sessions, sessions)&&(identical(other.isCurrent, isCurrent) || other.isCurrent == isCurrent));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(label),userAgent,deviceId,platform,const DeepCollectionEquality().hash(sessions),isCurrent);
@override
String toString() {
return 'SnAuthDevice(label: $label, userAgent: $userAgent, deviceId: $deviceId, platform: $platform, sessions: $sessions, isCurrent: $isCurrent)';
}
}
/// @nodoc
abstract mixin class $SnAuthDeviceCopyWith<$Res> {
factory $SnAuthDeviceCopyWith(SnAuthDevice value, $Res Function(SnAuthDevice) _then) = _$SnAuthDeviceCopyWithImpl;
@useResult
$Res call({
dynamic label, String userAgent, String deviceId, int platform, List<SnAuthSession> sessions, bool isCurrent
});
}
/// @nodoc
class _$SnAuthDeviceCopyWithImpl<$Res>
implements $SnAuthDeviceCopyWith<$Res> {
_$SnAuthDeviceCopyWithImpl(this._self, this._then);
final SnAuthDevice _self;
final $Res Function(SnAuthDevice) _then;
/// Create a copy of SnAuthDevice
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? label = freezed,Object? userAgent = null,Object? deviceId = null,Object? platform = null,Object? sessions = null,Object? isCurrent = null,}) {
return _then(_self.copyWith(
label: freezed == label ? _self.label : label // ignore: cast_nullable_to_non_nullable
as dynamic,userAgent: null == userAgent ? _self.userAgent : userAgent // ignore: cast_nullable_to_non_nullable
as String,deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable
as String,platform: null == platform ? _self.platform : platform // ignore: cast_nullable_to_non_nullable
as int,sessions: null == sessions ? _self.sessions : sessions // ignore: cast_nullable_to_non_nullable
as List<SnAuthSession>,isCurrent: null == isCurrent ? _self.isCurrent : isCurrent // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
/// @nodoc
@JsonSerializable()
class _SnAuthDevice implements SnAuthDevice {
const _SnAuthDevice({required this.label, required this.userAgent, required this.deviceId, required this.platform, required final List<SnAuthSession> sessions, this.isCurrent = false}): _sessions = sessions;
factory _SnAuthDevice.fromJson(Map<String, dynamic> json) => _$SnAuthDeviceFromJson(json);
@override final dynamic label;
@override final String userAgent;
@override final String deviceId;
@override final int platform;
final List<SnAuthSession> _sessions;
@override List<SnAuthSession> get sessions {
if (_sessions is EqualUnmodifiableListView) return _sessions;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_sessions);
}
// Not from backend, used for UI
@override@JsonKey() final bool isCurrent;
/// Create a copy of SnAuthDevice
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$SnAuthDeviceCopyWith<_SnAuthDevice> get copyWith => __$SnAuthDeviceCopyWithImpl<_SnAuthDevice>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$SnAuthDeviceToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAuthDevice&&const DeepCollectionEquality().equals(other.label, label)&&(identical(other.userAgent, userAgent) || other.userAgent == userAgent)&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.platform, platform) || other.platform == platform)&&const DeepCollectionEquality().equals(other._sessions, _sessions)&&(identical(other.isCurrent, isCurrent) || other.isCurrent == isCurrent));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(label),userAgent,deviceId,platform,const DeepCollectionEquality().hash(_sessions),isCurrent);
@override
String toString() {
return 'SnAuthDevice(label: $label, userAgent: $userAgent, deviceId: $deviceId, platform: $platform, sessions: $sessions, isCurrent: $isCurrent)';
}
}
/// @nodoc
abstract mixin class _$SnAuthDeviceCopyWith<$Res> implements $SnAuthDeviceCopyWith<$Res> {
factory _$SnAuthDeviceCopyWith(_SnAuthDevice value, $Res Function(_SnAuthDevice) _then) = __$SnAuthDeviceCopyWithImpl;
@override @useResult
$Res call({
dynamic label, String userAgent, String deviceId, int platform, List<SnAuthSession> sessions, bool isCurrent
});
}
/// @nodoc
class __$SnAuthDeviceCopyWithImpl<$Res>
implements _$SnAuthDeviceCopyWith<$Res> {
__$SnAuthDeviceCopyWithImpl(this._self, this._then);
final _SnAuthDevice _self;
final $Res Function(_SnAuthDevice) _then;
/// Create a copy of SnAuthDevice
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? label = freezed,Object? userAgent = null,Object? deviceId = null,Object? platform = null,Object? sessions = null,Object? isCurrent = null,}) {
return _then(_SnAuthDevice(
label: freezed == label ? _self.label : label // ignore: cast_nullable_to_non_nullable
as dynamic,userAgent: null == userAgent ? _self.userAgent : userAgent // ignore: cast_nullable_to_non_nullable
as String,deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable
as String,platform: null == platform ? _self.platform : platform // ignore: cast_nullable_to_non_nullable
as int,sessions: null == sessions ? _self._sessions : sessions // ignore: cast_nullable_to_non_nullable
as List<SnAuthSession>,isCurrent: null == isCurrent ? _self.isCurrent : isCurrent // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
// dart format on

View File

@ -19,18 +19,21 @@ _SnAuthChallenge _$SnAuthChallengeFromJson(Map<String, dynamic> json) =>
expiredAt: DateTime.parse(json['expired_at'] as String),
stepRemain: (json['step_remain'] as num).toInt(),
stepTotal: (json['step_total'] as num).toInt(),
failedAttempts: (json['failed_attempts'] as num).toInt(),
platform: (json['platform'] as num).toInt(),
type: (json['type'] as num).toInt(),
blacklistFactors:
(json['blacklist_factors'] as List<dynamic>)
.map((e) => e as String)
.toList(),
audiences:
(json['audiences'] as List<dynamic>).map((e) => e as String).toList(),
scopes:
(json['scopes'] as List<dynamic>).map((e) => e as String).toList(),
audiences: json['audiences'] as List<dynamic>,
scopes: json['scopes'] as List<dynamic>,
ipAddress: json['ip_address'] as String,
userAgent: json['user_agent'] as String,
deviceId: json['device_id'] as String?,
deviceId: json['device_id'] as String,
nonce: json['nonce'] as String?,
location: json['location'] as String?,
accountId: json['account_id'] as String,
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
deletedAt:
@ -45,6 +48,9 @@ Map<String, dynamic> _$SnAuthChallengeToJson(_SnAuthChallenge instance) =>
'expired_at': instance.expiredAt.toIso8601String(),
'step_remain': instance.stepRemain,
'step_total': instance.stepTotal,
'failed_attempts': instance.failedAttempts,
'platform': instance.platform,
'type': instance.type,
'blacklist_factors': instance.blacklistFactors,
'audiences': instance.audiences,
'scopes': instance.scopes,
@ -52,6 +58,41 @@ Map<String, dynamic> _$SnAuthChallengeToJson(_SnAuthChallenge instance) =>
'user_agent': instance.userAgent,
'device_id': instance.deviceId,
'nonce': instance.nonce,
'location': instance.location,
'account_id': instance.accountId,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
};
_SnAuthSession _$SnAuthSessionFromJson(Map<String, dynamic> json) =>
_SnAuthSession(
id: json['id'] as String,
label: json['label'] as String?,
lastGrantedAt: DateTime.parse(json['last_granted_at'] as String),
expiredAt: DateTime.parse(json['expired_at'] as String),
accountId: json['account_id'] as String,
challengeId: json['challenge_id'] as String,
challenge: SnAuthChallenge.fromJson(
json['challenge'] as Map<String, dynamic>,
),
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
deletedAt:
json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
);
Map<String, dynamic> _$SnAuthSessionToJson(_SnAuthSession instance) =>
<String, dynamic>{
'id': instance.id,
'label': instance.label,
'last_granted_at': instance.lastGrantedAt.toIso8601String(),
'expired_at': instance.expiredAt.toIso8601String(),
'account_id': instance.accountId,
'challenge_id': instance.challengeId,
'challenge': instance.challenge.toJson(),
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
@ -91,3 +132,26 @@ Map<String, dynamic> _$SnAuthFactorToJson(_SnAuthFactor instance) =>
'trustworthy': instance.trustworthy,
'created_response': instance.createdResponse,
};
_SnAuthDevice _$SnAuthDeviceFromJson(Map<String, dynamic> json) =>
_SnAuthDevice(
label: json['label'],
userAgent: json['user_agent'] as String,
deviceId: json['device_id'] as String,
platform: (json['platform'] as num).toInt(),
sessions:
(json['sessions'] as List<dynamic>)
.map((e) => SnAuthSession.fromJson(e as Map<String, dynamic>))
.toList(),
isCurrent: json['is_current'] as bool? ?? false,
);
Map<String, dynamic> _$SnAuthDeviceToJson(_SnAuthDevice instance) =>
<String, dynamic>{
'label': instance.label,
'user_agent': instance.userAgent,
'device_id': instance.deviceId,
'platform': instance.platform,
'sessions': instance.sessions.map((e) => e.toJson()).toList(),
'is_current': instance.isCurrent,
};

View File

@ -16,6 +16,7 @@ import 'package:island/pods/userinfo.dart';
import 'package:island/screens/auth/captcha.dart';
import 'package:island/screens/auth/login.dart';
import 'package:island/services/responsive.dart';
import 'package:island/widgets/account/account_session_sheet.dart';
import 'package:island/widgets/alert.dart';
import 'package:island/widgets/app_scaffold.dart';
import 'package:island/widgets/content/sheet.dart';
@ -90,6 +91,21 @@ class AccountSettingsScreen extends HookConsumerWidget {
// Group settings into categories for better organization
final securitySettings = [
ListTile(
minLeadingWidth: 48,
leading: const Icon(Symbols.devices),
title: Text('authSessions').tr(),
subtitle: Text('authSessionsDescription').tr().fontSize(12),
contentPadding: const EdgeInsets.only(left: 24, right: 17),
trailing: const Icon(Symbols.chevron_right),
onTap: () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) => const AccountSessionSheet(),
);
},
),
ExpansionTile(
leading: const Icon(
Symbols.security,

View File

@ -1,8 +1,11 @@
import 'dart:convert';
import 'dart:io';
import 'dart:math' as math;
import 'package:animations/animations.dart';
import 'package:auto_route/auto_route.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
@ -63,6 +66,19 @@ class LoginScreen extends HookConsumerWidget {
LinearProgressIndicator(
minHeight: 4,
borderRadius: BorderRadius.zero,
trackGap: 0,
stopIndicatorRadius: 0,
)
else if (currentTicket.value != null)
LinearProgressIndicator(
minHeight: 4,
borderRadius: BorderRadius.zero,
trackGap: 0,
stopIndicatorRadius: 0,
value:
1 -
(currentTicket.value!.stepRemain /
currentTicket.value!.stepTotal),
)
else
const Gap(4),
@ -121,17 +137,8 @@ class LoginScreen extends HookConsumerWidget {
).padding(all: 24),
).center(),
),
if (currentTicket.value != null)
LinearProgressIndicator(
minHeight: 4,
borderRadius: BorderRadius.zero,
value:
1 -
(currentTicket.value!.stepRemain /
currentTicket.value!.stepTotal),
)
else
const Gap(4),
const Gap(4),
],
),
),
@ -170,6 +177,7 @@ class _LoginCheckScreen extends HookConsumerWidget {
if (pwd.isEmpty) return;
isBusy.value = true;
try {
// Pass challenge
final client = ref.watch(apiClientProvider);
final resp = await client.patch(
'/auth/challenge/${challenge!.id}',
@ -181,6 +189,8 @@ class _LoginCheckScreen extends HookConsumerWidget {
onNext();
return;
}
// Get token if challenge is completed
final tokenResp = await client.post(
'/auth/token',
data: {'grant_type': 'authorization_code', 'code': result.id},
@ -189,6 +199,8 @@ class _LoginCheckScreen extends HookConsumerWidget {
setToken(ref.watch(sharedPreferencesProvider), token);
ref.invalidate(tokenProvider);
if (!context.mounted) return;
// Do post login tasks
final userNotifier = ref.read(userInfoProvider.notifier);
userNotifier.fetchUser().then((_) {
final apiClient = ref.read(apiClientProvider);
@ -197,6 +209,31 @@ class _LoginCheckScreen extends HookConsumerWidget {
wsNotifier.connect();
if (context.mounted) Navigator.pop(context, true);
});
// Update the sessions' device name is available
if (!kIsWeb) {
String? name;
if (Platform.isIOS) {
return;
// TODO waiting for apple to respond to grant my access to com.apple.developer.device-information.user-assigned-device-name
// ignore: dead_code
final deviceInfo = await DeviceInfoPlugin().iosInfo;
name = deviceInfo.name;
} else if (Platform.isAndroid) {
final deviceInfo = await DeviceInfoPlugin().androidInfo;
name = deviceInfo.name;
} else if (Platform.isWindows) {
final deviceInfo = await DeviceInfoPlugin().windowsInfo;
name = deviceInfo.computerName;
}
if (name != null) {
final client = ref.watch(apiClientProvider);
await client.patch(
'/accounts/me/sessions/current/label',
data: jsonEncode(name),
);
}
}
} catch (err) {
showErrorAlert(err);
return;
@ -336,6 +373,14 @@ class _LoginPickerScreen extends HookConsumerWidget {
onPickFactor(factors!.where((x) => x == factorPicked.value).first);
onNext();
} catch (err) {
if (err is DioException && err.response?.statusCode == 400) {
onPickFactor(factors!.where((x) => x == factorPicked.value).first);
onNext();
if (context.mounted) {
showSnackBar(context, err.response!.data.toString());
}
return;
}
showErrorAlert(err);
return;
} finally {
@ -404,6 +449,7 @@ class _LoginPickerScreen extends HookConsumerWidget {
TextField(
controller: hintController,
decoration: InputDecoration(
isDense: true,
border: const OutlineInputBorder(),
labelText: 'authFactorHint'.tr(),
helperText: 'authFactorHintHelper'.tr(),

View File

@ -0,0 +1,246 @@
import 'dart:convert';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/auth.dart';
import 'package:island/pods/network.dart';
import 'package:island/services/responsive.dart';
import 'package:island/widgets/alert.dart';
import 'package:island/widgets/content/sheet.dart';
import 'package:island/widgets/response.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:styled_widget/styled_widget.dart';
part 'account_session_sheet.g.dart';
@riverpod
Future<List<SnAuthDevice>> authDevices(Ref ref) async {
final resp = await ref.watch(apiClientProvider).get('/accounts/me/devices');
final sessionId = resp.headers.value('x-auth-session');
final data =
resp.data.map<SnAuthDevice>((e) {
final ele = SnAuthDevice.fromJson(e);
return ele.copyWith(isCurrent: ele.sessions.first.id == sessionId);
}).toList();
return data;
}
class _DeviceListTile extends StatelessWidget {
final SnAuthDevice device;
final Function(String) updateDeviceLabel;
final Function(String) logoutDevice;
const _DeviceListTile({
required this.device,
required this.updateDeviceLabel,
required this.logoutDevice,
});
@override
Widget build(BuildContext context) {
return ListTile(
isThreeLine: true,
contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
leading: Icon(switch (device.platform) {
0 => Icons.device_unknown, // Unidentified
1 => Icons.web, // Web
2 => Icons.phone_iphone, // iOS
3 => Icons.phone_android, // Android
4 => Icons.laptop_mac, // macOS
5 => Icons.window, // Windows
6 => Icons.computer, // Linux
_ => Icons.device_unknown, // fallback
}).padding(top: 4),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text('authSessionsCount'.plural(device.sessions.length)),
Text(
'lastActiveAt'.tr(
args: [
DateFormat().format(
device.sessions.first.lastGrantedAt.toLocal(),
),
],
),
),
Text(device.sessions.first.challenge.ipAddress),
if (device.isCurrent)
Row(
children: [
Badge(
backgroundColor: Theme.of(context).colorScheme.primary,
label: Text(
'authDeviceCurrent'.tr(),
style: TextStyle(
color: Theme.of(context).colorScheme.onPrimary,
),
),
),
],
).padding(top: 4),
],
),
title: Text(device.label ?? device.sessions.first.challenge.userAgent),
trailing:
isWideScreen(context)
? Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: Icon(Icons.edit),
tooltip: 'authDeviceEditLabel'.tr(),
onPressed:
() => updateDeviceLabel(device.sessions.first.id),
),
if (!device.isCurrent)
IconButton(
icon: Icon(Icons.logout),
tooltip: 'authDeviceLogout'.tr(),
onPressed: () => logoutDevice(device.sessions.first.id),
),
],
)
: null,
);
}
}
class AccountSessionSheet extends HookConsumerWidget {
const AccountSessionSheet({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final authDevices = ref.watch(authDevicesProvider);
void logoutDevice(String sessionId) async {
final confirm = await showConfirmAlert(
'authDeviceLogoutHint'.tr(),
'authDeviceLogout'.tr(),
);
if (!confirm || !context.mounted) return;
try {
final apiClient = ref.watch(apiClientProvider);
await apiClient.delete('/accounts/me/sessions/$sessionId');
ref.invalidate(authDevicesProvider);
} catch (err) {
showErrorAlert(err);
}
}
void updateDeviceLabel(String sessionId) async {
final controller = TextEditingController();
final label = await showDialog<String>(
context: context,
builder:
(context) => AlertDialog(
title: Text('authDeviceLabelTitle'.tr()),
content: TextField(
controller: controller,
decoration: InputDecoration(
isDense: true,
border: const OutlineInputBorder(),
hintText: 'authDeviceLabelHint'.tr(),
),
autofocus: true,
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text('cancel'.tr()),
),
TextButton(
onPressed: () => Navigator.pop(context, controller.text),
child: Text('confirm'.tr()),
),
],
),
);
if (label == null || label.isEmpty || !context.mounted) return;
try {
final apiClient = ref.watch(apiClientProvider);
await apiClient.patch(
'/accounts/me/sessions/$sessionId/label',
data: jsonEncode(label),
);
ref.invalidate(authDevicesProvider);
} catch (err) {
showErrorAlert(err);
}
}
final wideScreen = isWideScreen(context);
return SheetScaffold(
titleText: 'authSessions'.tr(),
child: authDevices.when(
data:
(data) => RefreshIndicator(
onRefresh:
() => Future.sync(() => ref.invalidate(authDevicesProvider)),
child: ListView.builder(
padding: EdgeInsets.zero,
itemCount: data.length,
itemBuilder: (context, index) {
final device = data[index];
if (wideScreen) {
return _DeviceListTile(
device: device,
updateDeviceLabel: updateDeviceLabel,
logoutDevice: logoutDevice,
);
} else {
return Dismissible(
key: Key('device-${device.sessions.first.id}'),
direction:
device.isCurrent
? DismissDirection.startToEnd
: DismissDirection.horizontal,
background: Container(
color: Colors.blue,
alignment: Alignment.centerLeft,
padding: EdgeInsets.symmetric(horizontal: 20),
child: Icon(Icons.edit, color: Colors.white),
),
secondaryBackground: Container(
color: Colors.red,
alignment: Alignment.centerRight,
padding: EdgeInsets.symmetric(horizontal: 20),
child: Icon(Icons.logout, color: Colors.white),
),
confirmDismiss: (direction) async {
if (direction == DismissDirection.startToEnd) {
updateDeviceLabel(device.sessions.first.id);
return false;
} else {
final confirm = await showConfirmAlert(
'authDeviceLogoutHint'.tr(),
'authDeviceLogout'.tr(),
);
if (confirm && context.mounted) {
logoutDevice(device.sessions.first.id);
}
return false; // Don't dismiss
}
},
child: _DeviceListTile(
device: device,
updateDeviceLabel: updateDeviceLabel,
logoutDevice: logoutDevice,
),
);
}
},
),
),
error:
(err, _) => ResponseErrorWidget(
error: err,
onRetry: () => ref.invalidate(authDevicesProvider),
),
loading: () => ResponseLoadingWidget(),
),
);
}
}

View File

@ -0,0 +1,29 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'account_session_sheet.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$authDevicesHash() => r'9b8101167653991314efd37788d8416f414cb9e8';
/// See also [authDevices].
@ProviderFor(authDevices)
final authDevicesProvider =
AutoDisposeFutureProvider<List<SnAuthDevice>>.internal(
authDevices,
name: r'authDevicesProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$authDevicesHash,
dependencies: null,
allTransitiveDependencies: null,
);
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef AuthDevicesRef = AutoDisposeFutureProviderRef<List<SnAuthDevice>>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package