]>
Commit | Line | Data |
---|---|---|
0e468546 TM |
1 | import 'package:flutter/material.dart'; |
2 | import 'package:proxmox_dart_api_client/proxmox_dart_api_client.dart'; | |
3 | import 'package:proxmox_login_manager/proxmox_login_form.dart'; | |
4 | ||
5 | class ProxmoxTfaForm extends StatefulWidget { | |
a9d1ee22 | 6 | final ProxmoxApiClient? apiClient; |
0e468546 | 7 | |
39597066 | 8 | const ProxmoxTfaForm({super.key, this.apiClient}); |
0e468546 TM |
9 | |
10 | @override | |
f82ea945 | 11 | State<ProxmoxTfaForm> createState() => _ProxmoxTfaFormState(); |
0e468546 TM |
12 | } |
13 | ||
14 | class _ProxmoxTfaFormState extends State<ProxmoxTfaForm> { | |
15 | final TextEditingController _codeController = TextEditingController(); | |
16 | bool _isLoading = false; | |
44515cff TL |
17 | List<String> _tfaKinds = []; |
18 | String _selectedTfaKind = ""; | |
eb675700 TL |
19 | |
20 | @override | |
21 | void initState() { | |
22 | super.initState(); | |
44515cff TL |
23 | _tfaKinds = widget.apiClient!.credentials.tfa!.kinds().toList(); |
24 | _selectedTfaKind = _tfaKinds[0]; | |
eb675700 TL |
25 | } |
26 | ||
0e468546 TM |
27 | @override |
28 | Widget build(BuildContext context) { | |
29 | return Theme( | |
291fc65f | 30 | data: ThemeData.dark().copyWith( |
7c043135 | 31 | colorScheme: const ColorScheme.dark().copyWith( |
291fc65f DC |
32 | secondary: ProxmoxColors.orange, |
33 | onSecondary: ProxmoxColors.supportGrey)), | |
79ed04db | 34 | child: Scaffold( |
291fc65f | 35 | backgroundColor: ProxmoxColors.supportBlue, |
79ed04db TM |
36 | extendBodyBehindAppBar: true, |
37 | appBar: AppBar( | |
38 | elevation: 0.0, | |
39 | backgroundColor: Colors.transparent, | |
40 | leading: IconButton( | |
7c043135 | 41 | icon: const Icon(Icons.close), |
79ed04db TM |
42 | onPressed: () => Navigator.of(context).pop(), |
43 | ), | |
44 | ), | |
45 | body: Stack( | |
0e468546 TM |
46 | alignment: Alignment.center, |
47 | children: [ | |
48 | SingleChildScrollView( | |
49 | child: ConstrainedBox( | |
50 | constraints: BoxConstraints.tightFor( | |
51 | height: MediaQuery.of(context).size.height), | |
0de76be7 TL |
52 | child: SafeArea( |
53 | child: Padding( | |
54 | padding: const EdgeInsets.all(8.0), | |
55 | child: Column( | |
56 | mainAxisAlignment: MainAxisAlignment.start, | |
57 | crossAxisAlignment: CrossAxisAlignment.center, | |
58 | children: <Widget>[ | |
7c043135 TL |
59 | const Padding( |
60 | padding: EdgeInsets.fromLTRB(0, 100.0, 0, 30.0), | |
0de76be7 TL |
61 | child: Icon( |
62 | Icons.lock, | |
63 | size: 48, | |
64 | ), | |
0e468546 | 65 | ), |
7c043135 | 66 | const Text( |
0de76be7 TL |
67 | 'Verify', |
68 | style: TextStyle( | |
69 | fontSize: 36, | |
70 | color: Colors.white, | |
71 | fontWeight: FontWeight.bold), | |
72 | ), | |
7c043135 | 73 | const Text( |
0de76be7 TL |
74 | 'Check your second factor provider', |
75 | style: TextStyle( | |
76 | color: Colors.white38, | |
77 | fontWeight: FontWeight.bold), | |
78 | ), | |
79 | Padding( | |
80 | padding: const EdgeInsets.fromLTRB(0, 50.0, 0, 8.0), | |
98bcbcae | 81 | child: SizedBox( |
0de76be7 TL |
82 | width: 175, |
83 | child: Column( | |
84 | children: <Widget>[ | |
85 | DropdownButtonFormField( | |
7c043135 | 86 | decoration: const InputDecoration( |
0de76be7 TL |
87 | labelText: 'Method', |
88 | icon: Icon(Icons.input)), | |
44515cff | 89 | items: _tfaKinds |
0de76be7 | 90 | .map((e) => DropdownMenuItem( |
0de76be7 | 91 | value: e, |
7824e4fc | 92 | child: ListTile(title: Text(e)), |
0de76be7 TL |
93 | )) |
94 | .toList(), | |
95 | onChanged: (String? value) { | |
96 | setState(() { | |
44515cff | 97 | _selectedTfaKind = value!; |
0de76be7 TL |
98 | }); |
99 | }, | |
100 | selectedItemBuilder: (context) => | |
44515cff TL |
101 | _tfaKinds.map((e) => Text(e)).toList(), |
102 | value: _selectedTfaKind, | |
0de76be7 TL |
103 | ), |
104 | TextField( | |
105 | controller: _codeController, | |
106 | textAlign: TextAlign.center, | |
7c043135 | 107 | decoration: const InputDecoration( |
0de76be7 TL |
108 | labelText: 'Code', |
109 | icon: Icon(Icons.pin)), | |
44515cff | 110 | keyboardType: _selectedTfaKind == 'totp' |
ccefe9ed TL |
111 | ? TextInputType.number |
112 | : TextInputType.visiblePassword, | |
0de76be7 TL |
113 | autofocus: true, |
114 | onSubmitted: (value) => _submitTfaCode()), | |
115 | ], | |
116 | ), | |
eb675700 | 117 | ), |
0e468546 | 118 | ), |
0de76be7 TL |
119 | Expanded( |
120 | child: Align( | |
121 | alignment: Alignment.bottomCenter, | |
98bcbcae | 122 | child: SizedBox( |
0de76be7 TL |
123 | width: MediaQuery.of(context).size.width, |
124 | child: TextButton( | |
125 | style: TextButton.styleFrom( | |
126 | foregroundColor: Colors.white, | |
7c043135 | 127 | backgroundColor: const Color(0xFFE47225), |
0de76be7 TL |
128 | disabledBackgroundColor: Colors.grey, |
129 | ), | |
130 | onPressed: () => _submitTfaCode(), | |
7c043135 | 131 | child: const Text('Continue'), |
fcdfb148 | 132 | ), |
0e468546 TM |
133 | ), |
134 | ), | |
135 | ), | |
0de76be7 TL |
136 | ], |
137 | ), | |
0e468546 TM |
138 | ), |
139 | ), | |
140 | ), | |
141 | ), | |
142 | if (_isLoading) | |
7c043135 | 143 | const ProxmoxProgressOverlay( |
aa8e7870 | 144 | message: 'Verifying second-factor...', |
0e468546 TM |
145 | ) |
146 | ], | |
147 | ), | |
148 | ), | |
149 | ); | |
150 | } | |
151 | ||
152 | Future<void> _submitTfaCode() async { | |
153 | setState(() { | |
154 | _isLoading = true; | |
155 | }); | |
156 | try { | |
eb675700 | 157 | final client = await widget.apiClient! |
44515cff | 158 | .finishTfaChallenge(_selectedTfaKind, _codeController.text); |
10b47afc | 159 | if (mounted) Navigator.of(context).pop(client); |
0e468546 | 160 | } on ProxmoxApiException catch (e) { |
10b47afc TL |
161 | if (mounted) { |
162 | showDialog( | |
163 | context: context, | |
164 | builder: (context) => ProxmoxApiErrorDialog( | |
165 | exception: e, | |
166 | ), | |
167 | ); | |
168 | } | |
0e468546 TM |
169 | } catch (e, trace) { |
170 | print(e); | |
171 | print(trace); | |
10b47afc TL |
172 | if (mounted) { |
173 | showDialog( | |
174 | context: context, | |
175 | builder: (context) => ConnectionErrorDialog( | |
176 | exception: e, | |
177 | ), | |
178 | ); | |
179 | } | |
0e468546 TM |
180 | } |
181 | setState(() { | |
182 | _isLoading = false; | |
183 | }); | |
184 | } | |
185 | } |