You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

AuthToken.vue 8.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. <!--
  2. - @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
  3. -
  4. - @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
  5. -
  6. - @license GNU AGPL version 3 or any later version
  7. -
  8. - This program is free software: you can redistribute it and/or modify
  9. - it under the terms of the GNU Affero General Public License as
  10. - published by the Free Software Foundation, either version 3 of the
  11. - License, or (at your option) any later version.
  12. -
  13. - This program is distributed in the hope that it will be useful,
  14. - but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. - GNU Affero General Public License for more details.
  17. -
  18. - You should have received a copy of the GNU Affero General Public License
  19. - along with this program. If not, see <http://www.gnu.org/licenses/>.
  20. -->
  21. <template>
  22. <tr :data-id="token.id"
  23. :class="wiping">
  24. <td class="client">
  25. <div :class="iconName.icon" />
  26. </td>
  27. <td class="token-name">
  28. <input v-if="token.canRename && renaming"
  29. ref="input"
  30. v-model="newName"
  31. type="text"
  32. @keyup.enter="rename"
  33. @blur="cancelRename"
  34. @keyup.esc="cancelRename">
  35. <span v-else>{{ iconName.name }}</span>
  36. <span v-if="wiping" class="wiping-warning">({{ t('settings', 'Marked for remote wipe') }})</span>
  37. </td>
  38. <td>
  39. <span v-tooltip="lastActivity" class="last-activity">{{ lastActivityRelative }}</span>
  40. </td>
  41. <td class="more">
  42. <Actions v-if="!token.current"
  43. v-tooltip.auto="{
  44. content: t('settings', 'Device settings'),
  45. container: 'body'
  46. }"
  47. :open.sync="actionOpen">
  48. <ActionCheckbox v-if="token.type === 1"
  49. :checked="token.scope.filesystem"
  50. @change.stop.prevent="$emit('toggleScope', token, 'filesystem', !token.scope.filesystem)">
  51. <!-- TODO: add text/longtext with some description -->
  52. {{ t('settings', 'Allow filesystem access') }}
  53. </ActionCheckbox>
  54. <ActionButton v-if="token.canRename"
  55. icon="icon-rename"
  56. @click.stop.prevent="startRename">
  57. <!-- TODO: add text/longtext with some description -->
  58. {{ t('settings', 'Rename') }}
  59. </ActionButton>
  60. <!-- revoke & wipe -->
  61. <template v-if="token.canDelete">
  62. <template v-if="token.type !== 2">
  63. <ActionButton icon="icon-delete"
  64. @click.stop.prevent="revoke">
  65. <!-- TODO: add text/longtext with some description -->
  66. {{ t('settings', 'Revoke') }}
  67. </ActionButton>
  68. <ActionButton icon="icon-delete"
  69. @click.stop.prevent="wipe">
  70. {{ t('settings', 'Wipe device') }}
  71. </ActionButton>
  72. </template>
  73. <ActionButton v-else-if="token.type === 2"
  74. icon="icon-delete"
  75. :title="t('settings', 'Revoke')"
  76. @click.stop.prevent="revoke">
  77. {{ t('settings', 'Revoking this token might prevent the wiping of your device if it hasn\'t started the wipe yet.') }}
  78. </ActionButton>
  79. </template>
  80. </Actions>
  81. </td>
  82. </tr>
  83. </template>
  84. <script>
  85. import {
  86. Actions,
  87. ActionButton,
  88. ActionCheckbox
  89. } from 'nextcloud-vue'
  90. const userAgentMap = {
  91. ie: /(?:MSIE|Trident|Trident\/7.0; rv)[ :](\d+)/,
  92. // Microsoft Edge User Agent from https://msdn.microsoft.com/en-us/library/hh869301(v=vs.85).aspx
  93. edge: /^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Chrome\/[0-9.]+ (?:Mobile Safari|Safari)\/[0-9.]+ Edge\/[0-9.]+$/,
  94. // Firefox User Agent from https://developer.mozilla.org/en-US/docs/Web/HTTP/Gecko_user_agent_string_reference
  95. firefox: /^Mozilla\/5\.0 \([^)]*(Windows|OS X|Linux)[^)]+\) Gecko\/[0-9.]+ Firefox\/(\d+)(?:\.\d)?$/,
  96. // Chrome User Agent from https://developer.chrome.com/multidevice/user-agent
  97. chrome: /^Mozilla\/5\.0 \([^)]*(Windows|OS X|Linux)[^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Chrome\/(\d+)[0-9.]+ (?:Mobile Safari|Safari)\/[0-9.]+$/,
  98. // Safari User Agent from http://www.useragentstring.com/pages/Safari/
  99. safari: /^Mozilla\/5\.0 \([^)]*(Windows|OS X)[^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\)(?: Version\/([0-9]+)[0-9.]+)? Safari\/[0-9.A-Z]+$/,
  100. // Android Chrome user agent: https://developers.google.com/chrome/mobile/docs/user-agent
  101. androidChrome: /Android.*(?:; (.*) Build\/).*Chrome\/(\d+)[0-9.]+/,
  102. iphone: / *CPU +iPhone +OS +([0-9]+)_(?:[0-9_])+ +like +Mac +OS +X */,
  103. ipad: /\(iPad; *CPU +OS +([0-9]+)_(?:[0-9_])+ +like +Mac +OS +X */,
  104. iosClient: /^Mozilla\/5\.0 \(iOS\) (ownCloud|Nextcloud)-iOS.*$/,
  105. androidClient: /^Mozilla\/5\.0 \(Android\) ownCloud-android.*$/,
  106. iosTalkClient: /^Mozilla\/5\.0 \(iOS\) Nextcloud-Talk.*$/,
  107. androidTalkClient: /^Mozilla\/5\.0 \(Android\) Nextcloud-Talk.*$/,
  108. // DAVdroid/1.2 (2016/07/03; dav4android; okhttp3) Android/6.0.1
  109. davDroid: /DAV(droid|x5)\/([0-9.]+)/,
  110. // 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)
  111. webPirate: /(Sailfish).*WebPirate\/(\d+)/,
  112. // Mozilla/5.0 (Maemo; Linux; U; Jolla; Sailfish; Mobile; rv:31.0) Gecko/31.0 Firefox/31.0 SailfishBrowser/1.0
  113. sailfishBrowser: /(Sailfish).*SailfishBrowser\/(\d+)/
  114. }
  115. const nameMap = {
  116. ie: t('setting', 'Internet Explorer'),
  117. edge: t('setting', 'Edge'),
  118. firefox: t('setting', 'Firefox'),
  119. chrome: t('setting', 'Google Chrome'),
  120. safari: t('setting', 'Safari'),
  121. androidChrome: t('setting', 'Google Chrome for Android'),
  122. iphone: t('setting', 'iPhone'),
  123. ipad: t('setting', 'iPad'),
  124. iosClient: t('setting', 'Nextcloud iOS app'),
  125. androidClient: t('setting', 'Nextcloud Android app'),
  126. iosTalkClient: t('setting', 'Nextcloud Talk for iOS'),
  127. androidTalkClient: t('setting', 'Nextcloud Talk for Android'),
  128. davDroid: 'DAVdroid',
  129. webPirate: 'WebPirate',
  130. sailfishBrowser: 'SailfishBrowser'
  131. }
  132. const iconMap = {
  133. ie: 'icon-desktop',
  134. edge: 'icon-desktop',
  135. firefox: 'icon-desktop',
  136. chrome: 'icon-desktop',
  137. safari: 'icon-desktop',
  138. androidChrome: 'icon-phone',
  139. iphone: 'icon-phone',
  140. ipad: 'icon-tablet',
  141. iosClient: 'icon-phone',
  142. androidClient: 'icon-phone',
  143. iosTalkClient: 'icon-phone',
  144. androidTalkClient: 'icon-phone',
  145. davDroid: 'icon-phone',
  146. webPirate: 'icon-link',
  147. sailfishBrowser: 'icon-link'
  148. }
  149. export default {
  150. name: 'AuthToken',
  151. components: {
  152. Actions,
  153. ActionButton,
  154. ActionCheckbox
  155. },
  156. props: {
  157. token: {
  158. type: Object,
  159. required: true
  160. }
  161. },
  162. data() {
  163. return {
  164. showMore: this.token.canScope || this.token.canDelete,
  165. renaming: false,
  166. newName: '',
  167. actionOpen: false
  168. }
  169. },
  170. computed: {
  171. lastActivityRelative() {
  172. return OC.Util.relativeModifiedDate(this.token.lastActivity * 1000)
  173. },
  174. lastActivity() {
  175. return OC.Util.formatDate(this.token.lastActivity * 1000, 'LLL')
  176. },
  177. iconName() {
  178. // pretty format sync client user agent
  179. let matches = this.token.name.match(/Mozilla\/5\.0 \((\w+)\) (?:mirall|csyncoC)\/(\d+\.\d+\.\d+)/)
  180. let icon = ''
  181. if (matches) {
  182. /* eslint-disable-next-line */
  183. this.token.name = t('settings', 'Sync client - {os}', {
  184. os: matches[1],
  185. version: matches[2]
  186. })
  187. icon = 'icon-desktop'
  188. }
  189. // preserve title for cases where we format it further
  190. const title = this.token.name
  191. let name = this.token.name
  192. for (let client in userAgentMap) {
  193. const matches = title.match(userAgentMap[client])
  194. if (matches) {
  195. if (matches[2] && matches[1]) { // version number and os
  196. name = nameMap[client] + ' ' + matches[2] + ' - ' + matches[1]
  197. } else if (matches[1]) { // only version number
  198. name = nameMap[client] + ' ' + matches[1]
  199. } else {
  200. name = nameMap[client]
  201. }
  202. icon = iconMap[client]
  203. }
  204. }
  205. if (this.token.current) {
  206. name = t('settings', 'This session')
  207. }
  208. return {
  209. icon,
  210. name
  211. }
  212. },
  213. wiping() {
  214. return this.token.type === 2
  215. }
  216. },
  217. methods: {
  218. startRename() {
  219. // Close action (popover menu)
  220. this.actionOpen = false
  221. this.newName = this.token.name
  222. this.renaming = true
  223. this.$nextTick(() => {
  224. this.$refs.input.select()
  225. })
  226. },
  227. cancelRename() {
  228. this.renaming = false
  229. },
  230. revoke() {
  231. this.actionOpen = false
  232. this.$emit('delete', this.token)
  233. },
  234. rename() {
  235. this.renaming = false
  236. this.$emit('rename', this.token, this.newName)
  237. },
  238. wipe() {
  239. this.actionOpen = false
  240. this.$emit('wipe', this.token)
  241. }
  242. }
  243. }
  244. </script>
  245. <style lang="scss" scoped>
  246. .wiping {
  247. background-color: var(--color-background-darker);
  248. }
  249. td {
  250. border-top: 1px solid var(--color-border);
  251. max-width: 200px;
  252. white-space: normal;
  253. vertical-align: middle;
  254. position: relative;
  255. &%icon {
  256. overflow: visible;
  257. position: relative;
  258. width: 44px;
  259. height: 44px;
  260. }
  261. &.token-name {
  262. padding: 10px 6px;
  263. &.token-rename {
  264. padding: 0;
  265. }
  266. input {
  267. width: 100%;
  268. margin: 0;
  269. }
  270. }
  271. &.token-name .wiping-warning {
  272. color: var(--color-text-lighter);
  273. }
  274. &.more {
  275. @extend %icon;
  276. padding: 0 10px;
  277. }
  278. &.client {
  279. @extend %icon;
  280. div {
  281. opacity: 0.57;
  282. width: 44px;
  283. height: 44px;
  284. }
  285. }
  286. }
  287. </style>