@@ -21,12 +21,15 @@ class AuthMFAEnrollResponse {
2121 });
2222
2323 factory AuthMFAEnrollResponse .fromJson (Map <String , dynamic > json) {
24- final type = FactorType .values.firstWhere ((e) => e.name == json['type' ]);
24+ final type = FactorType .values.firstWhere (
25+ (e) => e.name == json['type' ],
26+ orElse: () => FactorType .unknown,
27+ );
2528 return AuthMFAEnrollResponse (
26- id: json['id' ],
29+ id: json['id' ] as String ,
2730 type: type,
2831 totp: type == FactorType .totp && json['totp' ] != null
29- ? TOTPEnrollment .fromJson (json['totp' ])
32+ ? TOTPEnrollment .fromJson (json['totp' ] as Map < String , dynamic > )
3033 : null ,
3134 phone: type == FactorType .phone && json['phone' ] != null
3235 ? PhoneEnrollment ._fromJsonValue (json['phone' ])
@@ -57,9 +60,9 @@ class TOTPEnrollment {
5760
5861 factory TOTPEnrollment .fromJson (Map <String , dynamic > json) {
5962 return TOTPEnrollment (
60- qrCode: json['qr_code' ],
61- secret: json['secret' ],
62- uri: json['uri' ],
63+ qrCode: json['qr_code' ] as String ,
64+ secret: json['secret' ] as String ,
65+ uri: json['uri' ] as String ,
6366 );
6467 }
6568}
@@ -74,7 +77,7 @@ class PhoneEnrollment {
7477
7578 factory PhoneEnrollment .fromJson (Map <String , dynamic > json) {
7679 return PhoneEnrollment (
77- phone: json['phone' ],
80+ phone: json['phone' ] as String ,
7881 );
7982 }
8083
@@ -102,9 +105,17 @@ class AuthMFAChallengeResponse {
102105 const AuthMFAChallengeResponse ({required this .id, required this .expiresAt});
103106
104107 factory AuthMFAChallengeResponse .fromJson (Map <String , dynamic > json) {
108+ final expiresAtValue = json['expires_at' ];
109+ if (expiresAtValue is ! num ) {
110+ throw FormatException (
111+ 'Expected expires_at to be a number, got ${expiresAtValue .runtimeType }' ,
112+ json.toString (),
113+ );
114+ }
115+ final expiresAt = expiresAtValue.toInt ();
105116 return AuthMFAChallengeResponse (
106- id: json['id' ],
107- expiresAt: DateTime .fromMillisecondsSinceEpoch (json[ 'expires_at' ] * 1000 ),
117+ id: json['id' ] as String ,
118+ expiresAt: DateTime .fromMillisecondsSinceEpoch (expiresAt * 1000 ),
108119 );
109120 }
110121}
@@ -134,12 +145,34 @@ class AuthMFAVerifyResponse {
134145 });
135146
136147 factory AuthMFAVerifyResponse .fromJson (Map <String , dynamic > json) {
148+ final expiresInValue = json['expires_in' ];
149+ if (expiresInValue is ! num ) {
150+ throw FormatException (
151+ 'Expected expires_in to be a number, got ${expiresInValue .runtimeType }' ,
152+ json.toString (),
153+ );
154+ }
155+ final expiresIn = expiresInValue.toInt ();
156+ final userJson = json['user' ];
157+ if (userJson is ! Map <String , dynamic >) {
158+ throw FormatException (
159+ 'Expected user to be an object, got ${userJson .runtimeType }' ,
160+ json.toString (),
161+ );
162+ }
163+ final user = User .fromJson (userJson);
164+ if (user == null ) {
165+ throw FormatException (
166+ 'Failed to parse user object: missing required fields' ,
167+ json.toString (),
168+ );
169+ }
137170 return AuthMFAVerifyResponse (
138- accessToken: json['access_token' ],
139- tokenType: json['token_type' ],
140- expiresIn: Duration (seconds: json[ 'expires_in' ] ),
141- refreshToken: json['refresh_token' ],
142- user: User . fromJson (json[ ' user' ]) ! ,
171+ accessToken: json['access_token' ] as String ,
172+ tokenType: json['token_type' ] as String ,
173+ expiresIn: Duration (seconds: expiresIn ),
174+ refreshToken: json['refresh_token' ] as String ,
175+ user: user,
143176 );
144177 }
145178}
@@ -151,7 +184,7 @@ class AuthMFAUnenrollResponse {
151184 const AuthMFAUnenrollResponse ({required this .id});
152185
153186 factory AuthMFAUnenrollResponse .fromJson (Map <String , dynamic > json) {
154- return AuthMFAUnenrollResponse (id: json['id' ]);
187+ return AuthMFAUnenrollResponse (id: json['id' ] as String );
155188 }
156189}
157190
@@ -174,9 +207,17 @@ class AuthMFAAdminListFactorsResponse {
174207 const AuthMFAAdminListFactorsResponse ({required this .factors});
175208
176209 factory AuthMFAAdminListFactorsResponse .fromJson (Map <String , dynamic > json) {
210+ final factorsList = json['factors' ];
211+ if (factorsList is ! List ) {
212+ throw FormatException (
213+ 'Expected factors to be a list, got ${factorsList .runtimeType }' ,
214+ json.toString (),
215+ );
216+ }
177217 return AuthMFAAdminListFactorsResponse (
178- factors:
179- (json['factors' ] as List ).map ((e) => Factor .fromJson (e)).toList (),
218+ factors: factorsList
219+ .map ((e) => Factor .fromJson (e as Map <String , dynamic >))
220+ .toList (),
180221 );
181222 }
182223}
@@ -188,13 +229,27 @@ class AuthMFAAdminDeleteFactorResponse {
188229 const AuthMFAAdminDeleteFactorResponse ({required this .id});
189230
190231 factory AuthMFAAdminDeleteFactorResponse .fromJson (Map <String , dynamic > json) {
191- return AuthMFAAdminDeleteFactorResponse (id: json['id' ]);
232+ return AuthMFAAdminDeleteFactorResponse (id: json['id' ] as String );
192233 }
193234}
194235
195- enum FactorStatus { verified, unverified }
236+ enum FactorStatus {
237+ verified,
238+ unverified,
196239
197- enum FactorType { totp, phone }
240+ /// Returned when the backend sends an unknown status value.
241+ /// This allows forward compatibility with new status types.
242+ unknown,
243+ }
244+
245+ enum FactorType {
246+ totp,
247+ phone,
248+
249+ /// Returned when the backend sends an unknown factor type.
250+ /// This allows forward compatibility with new factor types.
251+ unknown,
252+ }
198253
199254class Factor {
200255 /// ID of the factor.
@@ -222,17 +277,37 @@ class Factor {
222277 });
223278
224279 factory Factor .fromJson (Map <String , dynamic > json) {
280+ DateTime parseDateTime (String key) {
281+ final value = json[key];
282+ if (value is ! String ) {
283+ throw FormatException (
284+ 'Expected $key to be a string, got ${value .runtimeType }' ,
285+ json.toString (),
286+ );
287+ }
288+ try {
289+ return DateTime .parse (value);
290+ } on FormatException {
291+ throw FormatException (
292+ 'Invalid date format for $key : $value ' ,
293+ json.toString (),
294+ );
295+ }
296+ }
297+
225298 return Factor (
226- id: json['id' ],
227- friendlyName: json['friendly_name' ],
299+ id: json['id' ] as String ,
300+ friendlyName: json['friendly_name' ] as String ? ,
228301 factorType: FactorType .values.firstWhere (
229302 (e) => e.name == json['factor_type' ],
303+ orElse: () => FactorType .unknown,
230304 ),
231305 status: FactorStatus .values.firstWhere (
232306 (e) => e.name == json['status' ],
307+ orElse: () => FactorStatus .unknown,
233308 ),
234- createdAt: DateTime . parse (json[ 'created_at' ] ),
235- updatedAt: DateTime . parse (json[ 'updated_at' ] ),
309+ createdAt: parseDateTime ( 'created_at' ),
310+ updatedAt: parseDateTime ( 'updated_at' ),
236311 );
237312 }
238313
@@ -337,12 +412,20 @@ class AMREntry {
337412 const AMREntry ({required this .method, required this .timestamp});
338413
339414 factory AMREntry .fromJson (Map <String , dynamic > json) {
415+ final timestampValue = json['timestamp' ];
416+ if (timestampValue is ! num ) {
417+ throw FormatException (
418+ 'Expected timestamp to be a number, got ${timestampValue .runtimeType }' ,
419+ json.toString (),
420+ );
421+ }
422+ final timestamp = timestampValue.toInt ();
340423 return AMREntry (
341424 method: AMRMethod .values.firstWhere (
342425 (e) => e.code == json['method' ],
343426 orElse: () => AMRMethod .unknown,
344427 ),
345- timestamp: DateTime .fromMillisecondsSinceEpoch (json[ ' timestamp' ] * 1000 ),
428+ timestamp: DateTime .fromMillisecondsSinceEpoch (timestamp * 1000 ),
346429 );
347430 }
348431}
0 commit comments