Skip to content

Commit a316007

Browse files
committed
Copy AuthTokenSection.vue and related from core/settings
Renamed "settings" to "simplesettings"
1 parent e523fc2 commit a316007

File tree

7 files changed

+1099
-0
lines changed

7 files changed

+1099
-0
lines changed

src/components/AuthToken.vue

Lines changed: 371 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,371 @@
1+
<!--
2+
- @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
3+
-
4+
- @author 2019 Christoph Wurst <[email protected]>
5+
- @author Ferdinand Thiessen <[email protected]>
6+
-
7+
- @license GNU AGPL version 3 or any later version
8+
-
9+
- This program is free software: you can redistribute it and/or modify
10+
- it under the terms of the GNU Affero General Public License as
11+
- published by the Free Software Foundation, either version 3 of the
12+
- License, or (at your option) any later version.
13+
-
14+
- This program is distributed in the hope that it will be useful,
15+
- but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
- GNU Affero General Public License for more details.
18+
-
19+
- You should have received a copy of the GNU Affero General Public License
20+
- along with this program. If not, see <http://www.gnu.org/licenses/>.
21+
-->
22+
23+
<template>
24+
<tr :class="['auth-token', { 'auth-token--wiping': wiping }]" :data-id="token.id">
25+
<td class="auth-token__name">
26+
<NcIconSvgWrapper :path="tokenIcon" />
27+
<div class="auth-token__name-wrapper">
28+
<form v-if="token.canRename && renaming"
29+
class="auth-token__name-form"
30+
@submit.prevent.stop="rename">
31+
<NcTextField ref="input"
32+
:value.sync="newName"
33+
:label="t('simplesettings', 'Device name')"
34+
:show-trailing-button="true"
35+
:trailing-button-label="t('simplesettings', 'Cancel renaming')"
36+
@trailing-button-click="cancelRename"
37+
@keyup.esc="cancelRename" />
38+
<NcButton :aria-label="t('simplesettings', 'Save new name')" type="tertiary" native-type="submit">
39+
<template #icon>
40+
<NcIconSvgWrapper :path="mdiCheck" />
41+
</template>
42+
</NcButton>
43+
</form>
44+
<span v-else>{{ tokenLabel }}</span>
45+
<span v-if="wiping" class="wiping-warning">({{ t('simplesettings', 'Marked for remote wipe') }})</span>
46+
</div>
47+
</td>
48+
<td>
49+
<NcDateTime class="auth-token__last-activity"
50+
:ignore-seconds="true"
51+
:timestamp="tokenLastActivity" />
52+
</td>
53+
<td class="auth-token__actions">
54+
<NcActions v-if="!token.current"
55+
:title="t('simplesettings', 'Device settings')"
56+
:aria-label="t('simplesettings', 'Device settings')"
57+
:open.sync="actionOpen">
58+
<NcActionCheckbox v-if="canChangeScope"
59+
:checked="token.scope.filesystem"
60+
@update:checked="updateFileSystemScope">
61+
<!-- TODO: add text/longtext with some description -->
62+
{{ t('simplesettings', 'Allow filesystem access') }}
63+
</NcActionCheckbox>
64+
<NcActionButton v-if="token.canRename"
65+
icon="icon-rename"
66+
@click.stop.prevent="startRename">
67+
<!-- TODO: add text/longtext with some description -->
68+
{{ t('simplesettings', 'Rename') }}
69+
</NcActionButton>
70+
71+
<!-- revoke & wipe -->
72+
<template v-if="token.canDelete">
73+
<template v-if="token.type !== 2">
74+
<NcActionButton icon="icon-delete"
75+
@click.stop.prevent="revoke">
76+
<!-- TODO: add text/longtext with some description -->
77+
{{ t('simplesettings', 'Revoke') }}
78+
</NcActionButton>
79+
<NcActionButton icon="icon-delete"
80+
@click.stop.prevent="wipe">
81+
{{ t('simplesettings', 'Wipe device') }}
82+
</NcActionButton>
83+
</template>
84+
<NcActionButton v-else-if="token.type === 2"
85+
icon="icon-delete"
86+
:name="t('simplesettings', 'Revoke')"
87+
@click.stop.prevent="revoke">
88+
{{ t('simplesettings', 'Revoking this token might prevent the wiping of your device if it has not started the wipe yet.') }}
89+
</NcActionButton>
90+
</template>
91+
</NcActions>
92+
</td>
93+
</tr>
94+
</template>
95+
96+
<script lang="ts">
97+
import type { PropType } from 'vue'
98+
import type { IToken } from '../store/authtoken'
99+
100+
import { mdiCheck, mdiCellphone, mdiTablet, mdiMonitor, mdiWeb, mdiKey, mdiMicrosoftEdge, mdiFirefox, mdiGoogleChrome, mdiAppleSafari, mdiAndroid, mdiAppleIos } from '@mdi/js'
101+
import { translate as t } from '@nextcloud/l10n'
102+
import { defineComponent } from 'vue'
103+
import { TokenType, useAuthTokenStore } from '../store/authtoken.ts'
104+
105+
import NcActions from '@nextcloud/vue/dist/Components/NcActions.js'
106+
import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
107+
import NcActionCheckbox from '@nextcloud/vue/dist/Components/NcActionCheckbox.js'
108+
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
109+
import NcDateTime from '@nextcloud/vue/dist/Components/NcDateTime.js'
110+
import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js'
111+
import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
112+
113+
// When using capture groups the following parts are extracted the first is used as the version number, the second as the OS
114+
const userAgentMap = {
115+
ie: /(?:MSIE|Trident|Trident\/7.0; rv)[ :](\d+)/,
116+
// Microsoft Edge User Agent from https://msdn.microsoft.com/en-us/library/hh869301(v=vs.85).aspx
117+
edge: /^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Chrome\/[0-9.]+ (?:Mobile Safari|Safari)\/[0-9.]+ Edge\/[0-9.]+$/,
118+
// Firefox User Agent from https://developer.mozilla.org/en-US/docs/Web/HTTP/Gecko_user_agent_string_reference
119+
firefox: /^Mozilla\/5\.0 \([^)]*(Windows|OS X|Linux)[^)]+\) Gecko\/[0-9.]+ Firefox\/(\d+)(?:\.\d)?$/,
120+
// Chrome User Agent from https://developer.chrome.com/multidevice/user-agent
121+
chrome: /^Mozilla\/5\.0 \([^)]*(Windows|OS X|Linux)[^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Chrome\/(\d+)[0-9.]+ (?:Mobile Safari|Safari)\/[0-9.]+$/,
122+
// Safari User Agent from http://www.useragentstring.com/pages/Safari/
123+
safari: /^Mozilla\/5\.0 \([^)]*(Windows|OS X)[^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\)(?: Version\/([0-9]+)[0-9.]+)? Safari\/[0-9.A-Z]+$/,
124+
// Android Chrome user agent: https://developers.google.com/chrome/mobile/docs/user-agent
125+
androidChrome: /Android.*(?:; (.*) Build\/).*Chrome\/(\d+)[0-9.]+/,
126+
iphone: / *CPU +iPhone +OS +([0-9]+)_(?:[0-9_])+ +like +Mac +OS +X */,
127+
ipad: /\(iPad; *CPU +OS +([0-9]+)_(?:[0-9_])+ +like +Mac +OS +X */,
128+
iosClient: /^Mozilla\/5\.0 \(iOS\) (?:ownCloud|Nextcloud)-iOS.*$/,
129+
androidClient: /^Mozilla\/5\.0 \(Android\) (?:ownCloud|Nextcloud)-android.*$/,
130+
iosTalkClient: /^Mozilla\/5\.0 \(iOS\) Nextcloud-Talk.*$/,
131+
androidTalkClient: /^Mozilla\/5\.0 \(Android\) Nextcloud-Talk.*$/,
132+
// DAVx5/3.3.8-beta2-gplay (2021/01/02; dav4jvm; okhttp/4.9.0) Android/10
133+
davx5: /DAV(?:droid|x5)\/([^ ]+)/,
134+
// Mozilla/5.0 (U; Linux; Maemo; Jolla; Sailfish; like Android 4.3) AppleWebKit/538.1 (KHTML, like Gecko) WebPirate/2.0 like Mobile Safari/538.1 (compatible)
135+
webPirate: /(Sailfish).*WebPirate\/(\d+)/,
136+
// Mozilla/5.0 (Maemo; Linux; U; Jolla; Sailfish; Mobile; rv:31.0) Gecko/31.0 Firefox/31.0 SailfishBrowser/1.0
137+
sailfishBrowser: /(Sailfish).*SailfishBrowser\/(\d+)/,
138+
// Neon 1.0.0+1
139+
neon: /Neon \d+\.\d+\.\d+\+\d+/,
140+
}
141+
const nameMap = {
142+
edge: 'Microsoft Edge',
143+
firefox: 'Firefox',
144+
chrome: 'Google Chrome',
145+
safari: 'Safari',
146+
androidChrome: t('simplesettings', 'Google Chrome for Android'),
147+
iphone: 'iPhone',
148+
ipad: 'iPad',
149+
iosClient: t('simplesettings', '{productName} iOS app', { productName: window.oc_defaults.productName }),
150+
androidClient: t('simplesettings', '{productName} Android app', { productName: window.oc_defaults.productName }),
151+
iosTalkClient: t('simplesettings', '{productName} Talk for iOS', { productName: window.oc_defaults.productName }),
152+
androidTalkClient: t('simplesettings', '{productName} Talk for Android', { productName: window.oc_defaults.productName }),
153+
syncClient: t('simplesettings', 'Sync client'),
154+
davx5: 'DAVx5',
155+
webPirate: 'WebPirate',
156+
sailfishBrowser: 'SailfishBrowser',
157+
neon: 'Neon',
158+
}
159+
160+
export default defineComponent({
161+
name: 'AuthToken',
162+
components: {
163+
NcActions,
164+
NcActionButton,
165+
NcActionCheckbox,
166+
NcButton,
167+
NcDateTime,
168+
NcIconSvgWrapper,
169+
NcTextField,
170+
},
171+
props: {
172+
token: {
173+
type: Object as PropType<IToken>,
174+
required: true,
175+
},
176+
},
177+
setup() {
178+
const authTokenStore = useAuthTokenStore()
179+
return { authTokenStore }
180+
},
181+
data() {
182+
return {
183+
actionOpen: false,
184+
renaming: false,
185+
newName: '',
186+
oldName: '',
187+
mdiCheck,
188+
}
189+
},
190+
computed: {
191+
canChangeScope() {
192+
return this.token.type === TokenType.PERMANENT_TOKEN
193+
},
194+
/**
195+
* Object ob the current user agend used by the token
196+
* @return Either an object containing user agent information or null if unknown
197+
*/
198+
client() {
199+
// pretty format sync client user agent
200+
const matches = this.token.name.match(/Mozilla\/5\.0 \((\w+)\) (?:mirall|csyncoC)\/(\d+\.\d+\.\d+)/)
201+
202+
if (matches) {
203+
return {
204+
id: 'syncClient',
205+
os: matches[1],
206+
version: matches[2],
207+
}
208+
}
209+
210+
for (const client in userAgentMap) {
211+
const matches = this.token.name.match(userAgentMap[client])
212+
if (matches) {
213+
return {
214+
id: client,
215+
os: matches[2] && matches[1],
216+
version: matches[2] ?? matches[1],
217+
}
218+
}
219+
}
220+
221+
return null
222+
},
223+
/**
224+
* Last activity of the token as ECMA timestamp (in ms)
225+
*/
226+
tokenLastActivity() {
227+
return this.token.lastActivity * 1000
228+
},
229+
/**
230+
* Icon to use for the current token
231+
*/
232+
tokenIcon() {
233+
// For custom created app tokens / app passwords
234+
if (this.token.type === TokenType.PERMANENT_TOKEN) {
235+
return mdiKey
236+
}
237+
238+
switch (this.client?.id) {
239+
case 'edge':
240+
return mdiMicrosoftEdge
241+
case 'firefox':
242+
return mdiFirefox
243+
case 'chrome':
244+
return mdiGoogleChrome
245+
case 'safari':
246+
return mdiAppleSafari
247+
case 'androidChrome':
248+
case 'androidClient':
249+
case 'androidTalkClient':
250+
return mdiAndroid
251+
case 'iphone':
252+
case 'iosClient':
253+
case 'iosTalkClient':
254+
return mdiAppleIos
255+
case 'ipad':
256+
return mdiTablet
257+
case 'davx5':
258+
return mdiCellphone
259+
case 'syncClient':
260+
return mdiMonitor
261+
case 'webPirate':
262+
case 'sailfishBrowser':
263+
default:
264+
return mdiWeb
265+
}
266+
},
267+
/**
268+
* Label to be shown for current token
269+
*/
270+
tokenLabel() {
271+
if (this.token.current) {
272+
return t('simplesettings', 'This session')
273+
}
274+
if (this.client === null) {
275+
return this.token.name
276+
}
277+
278+
const name = nameMap[this.client.id]
279+
if (this.client.os) {
280+
return t('simplesettings', '{client} - {version} ({system})', { client: name, system: this.client.os, version: this.client.version })
281+
} else if (this.client.version) {
282+
return t('simplesettings', '{client} - {version}', { client: name, version: this.client.version })
283+
}
284+
return name
285+
},
286+
/**
287+
* If the current token is considered for remote wiping
288+
*/
289+
wiping() {
290+
return this.token.type === TokenType.WIPING_TOKEN
291+
},
292+
},
293+
methods: {
294+
t,
295+
updateFileSystemScope(state: boolean) {
296+
this.authTokenStore.setTokenScope(this.token, 'filesystem', state)
297+
},
298+
startRename() {
299+
// Close action (popover menu)
300+
this.actionOpen = false
301+
302+
this.oldName = this.token.name
303+
this.newName = this.token.name
304+
this.renaming = true
305+
this.$nextTick(() => {
306+
this.$refs.input!.select()
307+
})
308+
},
309+
cancelRename() {
310+
this.renaming = false
311+
},
312+
revoke() {
313+
this.actionOpen = false
314+
this.authTokenStore.deleteToken(this.token)
315+
},
316+
rename() {
317+
this.renaming = false
318+
this.authTokenStore.renameToken(this.token, this.newName)
319+
},
320+
wipe() {
321+
this.actionOpen = false
322+
this.authTokenStore.wipeToken(this.token)
323+
},
324+
},
325+
})
326+
</script>
327+
328+
<style lang="scss" scoped>
329+
.auth-token {
330+
border-top: 2px solid var(--color-border);
331+
max-width: 200px;
332+
white-space: normal;
333+
vertical-align: middle;
334+
position: relative;
335+
336+
&--wiping {
337+
background-color: var(--color-background-dark);
338+
}
339+
340+
&__name {
341+
padding-block: 10px;
342+
display: flex;
343+
align-items: center;
344+
gap: 6px;
345+
min-width: 355px; // ensure no jumping when renaming
346+
}
347+
348+
&__name-wrapper {
349+
display: flex;
350+
flex-direction: column;
351+
}
352+
353+
&__name-form {
354+
align-items: end;
355+
display: flex;
356+
gap: 4px;
357+
}
358+
359+
&__actions {
360+
padding: 0 10px;
361+
}
362+
363+
&__last-activity {
364+
padding-inline-start: 10px;
365+
}
366+
367+
.wiping-warning {
368+
color: var(--color-text-maxcontrast);
369+
}
370+
}
371+
</style>

0 commit comments

Comments
 (0)