@jbrowse/plugin-authentication 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/LICENSE +201 -0
  2. package/dist/DropboxOAuthModel/configSchema.d.ts +5 -0
  3. package/dist/DropboxOAuthModel/configSchema.js +69 -0
  4. package/dist/DropboxOAuthModel/configSchema.js.map +1 -0
  5. package/dist/DropboxOAuthModel/index.d.ts +2 -0
  6. package/dist/DropboxOAuthModel/index.js +11 -0
  7. package/dist/DropboxOAuthModel/index.js.map +1 -0
  8. package/dist/DropboxOAuthModel/model.d.ts +98 -0
  9. package/dist/DropboxOAuthModel/model.js +103 -0
  10. package/dist/DropboxOAuthModel/model.js.map +1 -0
  11. package/dist/ExternalTokenModel/ExternalTokenEntryForm.d.ts +5 -0
  12. package/dist/ExternalTokenModel/ExternalTokenEntryForm.js +59 -0
  13. package/dist/ExternalTokenModel/ExternalTokenEntryForm.js.map +1 -0
  14. package/dist/ExternalTokenModel/configSchema.d.ts +5 -0
  15. package/dist/ExternalTokenModel/configSchema.js +26 -0
  16. package/dist/ExternalTokenModel/configSchema.js.map +1 -0
  17. package/dist/ExternalTokenModel/index.d.ts +2 -0
  18. package/dist/ExternalTokenModel/index.js +11 -0
  19. package/dist/ExternalTokenModel/index.js.map +1 -0
  20. package/dist/ExternalTokenModel/model.d.ts +67 -0
  21. package/dist/ExternalTokenModel/model.js +59 -0
  22. package/dist/ExternalTokenModel/model.js.map +1 -0
  23. package/dist/GoogleDriveOAuthModel/configSchema.d.ts +5 -0
  24. package/dist/GoogleDriveOAuthModel/configSchema.js +53 -0
  25. package/dist/GoogleDriveOAuthModel/configSchema.js.map +1 -0
  26. package/dist/GoogleDriveOAuthModel/index.d.ts +2 -0
  27. package/dist/GoogleDriveOAuthModel/index.js +11 -0
  28. package/dist/GoogleDriveOAuthModel/index.js.map +1 -0
  29. package/dist/GoogleDriveOAuthModel/model.d.ts +111 -0
  30. package/dist/GoogleDriveOAuthModel/model.js +115 -0
  31. package/dist/GoogleDriveOAuthModel/model.js.map +1 -0
  32. package/dist/HTTPBasicModel/HTTPBasicLoginForm.d.ts +5 -0
  33. package/dist/HTTPBasicModel/HTTPBasicLoginForm.js +55 -0
  34. package/dist/HTTPBasicModel/HTTPBasicLoginForm.js.map +1 -0
  35. package/dist/HTTPBasicModel/configSchema.d.ts +5 -0
  36. package/dist/HTTPBasicModel/configSchema.js +34 -0
  37. package/dist/HTTPBasicModel/configSchema.js.map +1 -0
  38. package/dist/HTTPBasicModel/index.d.ts +2 -0
  39. package/dist/HTTPBasicModel/index.js +11 -0
  40. package/dist/HTTPBasicModel/index.js.map +1 -0
  41. package/dist/HTTPBasicModel/model.d.ts +67 -0
  42. package/dist/HTTPBasicModel/model.js +59 -0
  43. package/dist/HTTPBasicModel/model.js.map +1 -0
  44. package/dist/OAuthModel/configSchema.d.ts +5 -0
  45. package/dist/OAuthModel/configSchema.js +90 -0
  46. package/dist/OAuthModel/configSchema.js.map +1 -0
  47. package/dist/OAuthModel/index.d.ts +2 -0
  48. package/dist/OAuthModel/index.js +11 -0
  49. package/dist/OAuthModel/index.js.map +1 -0
  50. package/dist/OAuthModel/model.d.ts +91 -0
  51. package/dist/OAuthModel/model.js +317 -0
  52. package/dist/OAuthModel/model.js.map +1 -0
  53. package/dist/index.d.ts +399 -0
  54. package/dist/index.js +80 -0
  55. package/dist/index.js.map +1 -0
  56. package/esm/DropboxOAuthModel/configSchema.d.ts +5 -0
  57. package/esm/DropboxOAuthModel/configSchema.js +64 -0
  58. package/esm/DropboxOAuthModel/configSchema.js.map +1 -0
  59. package/esm/DropboxOAuthModel/index.d.ts +2 -0
  60. package/esm/DropboxOAuthModel/index.js +3 -0
  61. package/esm/DropboxOAuthModel/index.js.map +1 -0
  62. package/esm/DropboxOAuthModel/model.d.ts +98 -0
  63. package/esm/DropboxOAuthModel/model.js +96 -0
  64. package/esm/DropboxOAuthModel/model.js.map +1 -0
  65. package/esm/ExternalTokenModel/ExternalTokenEntryForm.d.ts +5 -0
  66. package/esm/ExternalTokenModel/ExternalTokenEntryForm.js +29 -0
  67. package/esm/ExternalTokenModel/ExternalTokenEntryForm.js.map +1 -0
  68. package/esm/ExternalTokenModel/configSchema.d.ts +5 -0
  69. package/esm/ExternalTokenModel/configSchema.js +24 -0
  70. package/esm/ExternalTokenModel/configSchema.js.map +1 -0
  71. package/esm/ExternalTokenModel/index.d.ts +2 -0
  72. package/esm/ExternalTokenModel/index.js +3 -0
  73. package/esm/ExternalTokenModel/index.js.map +1 -0
  74. package/esm/ExternalTokenModel/model.d.ts +67 -0
  75. package/esm/ExternalTokenModel/model.js +57 -0
  76. package/esm/ExternalTokenModel/model.js.map +1 -0
  77. package/esm/GoogleDriveOAuthModel/configSchema.d.ts +5 -0
  78. package/esm/GoogleDriveOAuthModel/configSchema.js +48 -0
  79. package/esm/GoogleDriveOAuthModel/configSchema.js.map +1 -0
  80. package/esm/GoogleDriveOAuthModel/index.d.ts +2 -0
  81. package/esm/GoogleDriveOAuthModel/index.js +3 -0
  82. package/esm/GoogleDriveOAuthModel/index.js.map +1 -0
  83. package/esm/GoogleDriveOAuthModel/model.d.ts +111 -0
  84. package/esm/GoogleDriveOAuthModel/model.js +108 -0
  85. package/esm/GoogleDriveOAuthModel/model.js.map +1 -0
  86. package/esm/HTTPBasicModel/HTTPBasicLoginForm.d.ts +5 -0
  87. package/esm/HTTPBasicModel/HTTPBasicLoginForm.js +28 -0
  88. package/esm/HTTPBasicModel/HTTPBasicLoginForm.js.map +1 -0
  89. package/esm/HTTPBasicModel/configSchema.d.ts +5 -0
  90. package/esm/HTTPBasicModel/configSchema.js +32 -0
  91. package/esm/HTTPBasicModel/configSchema.js.map +1 -0
  92. package/esm/HTTPBasicModel/index.d.ts +2 -0
  93. package/esm/HTTPBasicModel/index.js +3 -0
  94. package/esm/HTTPBasicModel/index.js.map +1 -0
  95. package/esm/HTTPBasicModel/model.d.ts +67 -0
  96. package/esm/HTTPBasicModel/model.js +57 -0
  97. package/esm/HTTPBasicModel/model.js.map +1 -0
  98. package/esm/OAuthModel/configSchema.d.ts +5 -0
  99. package/esm/OAuthModel/configSchema.js +88 -0
  100. package/esm/OAuthModel/configSchema.js.map +1 -0
  101. package/esm/OAuthModel/index.d.ts +2 -0
  102. package/esm/OAuthModel/index.js +3 -0
  103. package/esm/OAuthModel/index.js.map +1 -0
  104. package/esm/OAuthModel/model.d.ts +91 -0
  105. package/esm/OAuthModel/model.js +289 -0
  106. package/esm/OAuthModel/model.js.map +1 -0
  107. package/esm/index.d.ts +399 -0
  108. package/esm/index.js +64 -0
  109. package/esm/index.js.map +1 -0
  110. package/package.json +63 -0
  111. package/src/DropboxOAuthModel/configSchema.ts +77 -0
  112. package/src/DropboxOAuthModel/index.ts +2 -0
  113. package/src/DropboxOAuthModel/model.tsx +141 -0
  114. package/src/ExternalTokenModel/ExternalTokenEntryForm.tsx +61 -0
  115. package/src/ExternalTokenModel/configSchema.ts +36 -0
  116. package/src/ExternalTokenModel/index.ts +2 -0
  117. package/src/ExternalTokenModel/model.tsx +70 -0
  118. package/src/GoogleDriveOAuthModel/configSchema.ts +61 -0
  119. package/src/GoogleDriveOAuthModel/index.ts +2 -0
  120. package/src/GoogleDriveOAuthModel/model.tsx +174 -0
  121. package/src/HTTPBasicModel/HTTPBasicLoginForm.tsx +71 -0
  122. package/src/HTTPBasicModel/configSchema.ts +43 -0
  123. package/src/HTTPBasicModel/index.ts +2 -0
  124. package/src/HTTPBasicModel/model.tsx +70 -0
  125. package/src/OAuthModel/configSchema.ts +98 -0
  126. package/src/OAuthModel/index.ts +2 -0
  127. package/src/OAuthModel/model.tsx +357 -0
  128. package/src/__snapshots__/index.test.js.snap +8 -0
  129. package/src/index.test.js +96 -0
  130. package/src/index.ts +97 -0
@@ -0,0 +1,141 @@
1
+ import React from 'react'
2
+ import { ConfigurationReference } from '@jbrowse/core/configuration'
3
+ import { UriLocation } from '@jbrowse/core/util/types'
4
+ import { SvgIconProps, SvgIcon } from '@mui/material'
5
+ import { Instance, types } from 'mobx-state-tree'
6
+ import { DropboxOAuthInternetAccountConfigModel } from './configSchema'
7
+ import baseModel from '../OAuthModel/model'
8
+ import { configSchema as OAuthConfigSchema } from '../OAuthModel'
9
+
10
+ interface DropboxError {
11
+ error_summary: string
12
+ error: {
13
+ '.tag': string
14
+ }
15
+ }
16
+
17
+ /** Error messages from https://www.dropbox.com/developers/documentation/http/documentation#sharing-get_shared_link_file */
18
+ const dropboxErrorMessages: Record<string, string | undefined> = {
19
+ shared_link_not_found: "The shared link wasn't found.",
20
+ shared_link_access_denied:
21
+ 'The caller is not allowed to access this shared link.',
22
+ unsupported_link_type:
23
+ 'This type of link is not supported; use files/export instead.',
24
+ shared_link_is_directory: 'Directories cannot be retrieved by this endpoint.',
25
+ }
26
+
27
+ export function DropboxIcon(props: SvgIconProps) {
28
+ return (
29
+ <SvgIcon {...props}>
30
+ <path d="M3 6.2L8 9.39L13 6.2L8 3L3 6.2M13 6.2L18 9.39L23 6.2L18 3L13 6.2M3 12.55L8 15.74L13 12.55L8 9.35L3 12.55M18 9.35L13 12.55L18 15.74L23 12.55L18 9.35M8.03 16.8L13.04 20L18.04 16.8L13.04 13.61L8.03 16.8Z" />
31
+ </SvgIcon>
32
+ )
33
+ }
34
+
35
+ async function getDescriptiveErrorMessage(response: Response) {
36
+ let errorMessage
37
+ try {
38
+ errorMessage = await response.text()
39
+ } catch (error) {
40
+ errorMessage = ''
41
+ }
42
+ if (errorMessage) {
43
+ let errorMessageParsed: DropboxError | undefined
44
+ try {
45
+ errorMessageParsed = JSON.parse(errorMessage)
46
+ } catch (error) {
47
+ errorMessageParsed = undefined
48
+ }
49
+ if (errorMessageParsed) {
50
+ const messageTag = errorMessageParsed.error['.tag']
51
+ errorMessage = dropboxErrorMessages[messageTag] || messageTag
52
+ }
53
+ }
54
+ return `Network response failure — ${response.status} (${
55
+ response.statusText
56
+ })${errorMessage ? ` (${errorMessage})` : ''}`
57
+ }
58
+
59
+ const stateModelFactory = (
60
+ configSchema: DropboxOAuthInternetAccountConfigModel,
61
+ ) => {
62
+ return baseModel(OAuthConfigSchema)
63
+ .named('DropboxOAuthInternetAccount')
64
+ .props({
65
+ type: types.literal('DropboxOAuthInternetAccount'),
66
+ configuration: ConfigurationReference(configSchema),
67
+ })
68
+ .views(() => ({
69
+ get toggleContents() {
70
+ return <DropboxIcon />
71
+ },
72
+ get selectorLabel() {
73
+ return 'Enter Dropbox share link'
74
+ },
75
+ }))
76
+ .actions(self => ({
77
+ getFetcher(
78
+ location?: UriLocation,
79
+ ): (input: RequestInfo, init?: RequestInit) => Promise<Response> {
80
+ return async (
81
+ input: RequestInfo,
82
+ init?: RequestInit,
83
+ ): Promise<Response> => {
84
+ const authToken = await self.getToken(location)
85
+ const newInit = self.addAuthHeaderToInit(
86
+ { ...init, method: 'POST' },
87
+ authToken,
88
+ )
89
+ newInit.headers.append(
90
+ 'Dropbox-API-Arg',
91
+ JSON.stringify({ url: input }),
92
+ )
93
+ const response = await fetch(
94
+ 'https://content.dropboxapi.com/2/sharing/get_shared_link_file',
95
+ newInit,
96
+ )
97
+ if (!response.ok) {
98
+ const message = await getDescriptiveErrorMessage(response)
99
+ throw new Error(message)
100
+ }
101
+ return response
102
+ }
103
+ },
104
+ async validateToken(
105
+ token: string,
106
+ location: UriLocation,
107
+ ): Promise<string> {
108
+ const response = await fetch(
109
+ 'https://api.dropboxapi.com/2/sharing/get_shared_link_metadata',
110
+ {
111
+ method: 'POST',
112
+ headers: {
113
+ Authorization: `Bearer ${token}`,
114
+ 'Content-Type': 'application/json',
115
+ },
116
+ body: JSON.stringify({
117
+ url: location.uri,
118
+ }),
119
+ },
120
+ )
121
+ if (!response.ok) {
122
+ const refreshToken =
123
+ self.hasRefreshToken && self.retrieveRefreshToken()
124
+ if (refreshToken) {
125
+ self.removeRefreshToken()
126
+ const newToken = await self.exchangeRefreshForAccessToken(
127
+ refreshToken,
128
+ )
129
+ return this.validateToken(newToken, location)
130
+ }
131
+ const message = await getDescriptiveErrorMessage(response)
132
+ throw new Error(`Token could not be validated. ${message}`)
133
+ }
134
+ return token
135
+ },
136
+ }))
137
+ }
138
+
139
+ export default stateModelFactory
140
+ export type DropboxOAuthStateModel = ReturnType<typeof stateModelFactory>
141
+ export type DropboxOAuthModel = Instance<DropboxOAuthStateModel>
@@ -0,0 +1,61 @@
1
+ import React, { useState } from 'react'
2
+ import Button from '@mui/material/Button'
3
+ import Dialog from '@mui/material/Dialog'
4
+ import DialogContent from '@mui/material/DialogContent'
5
+ import DialogTitle from '@mui/material/DialogTitle'
6
+ import DialogActions from '@mui/material/DialogActions'
7
+ import TextField from '@mui/material/TextField'
8
+
9
+ export const ExternalTokenEntryForm = ({
10
+ internetAccountId,
11
+ handleClose,
12
+ }: {
13
+ internetAccountId: string
14
+ handleClose: (token?: string) => void
15
+ }) => {
16
+ const [token, setToken] = useState('')
17
+
18
+ return (
19
+ <>
20
+ <Dialog open maxWidth="xl" data-testid="externalToken-form">
21
+ <DialogTitle>Enter Token for {internetAccountId}</DialogTitle>
22
+ <DialogContent style={{ display: 'flex', flexDirection: 'column' }}>
23
+ <TextField
24
+ required
25
+ label="Enter Token"
26
+ variant="outlined"
27
+ inputProps={{ 'data-testid': 'entry-externalToken' }}
28
+ onChange={event => {
29
+ setToken(event.target.value)
30
+ }}
31
+ margin="dense"
32
+ />
33
+ </DialogContent>
34
+ <DialogActions>
35
+ <Button
36
+ variant="contained"
37
+ color="primary"
38
+ type="submit"
39
+ disabled={!token}
40
+ onClick={() => {
41
+ if (token) {
42
+ handleClose(token)
43
+ }
44
+ }}
45
+ >
46
+ Add
47
+ </Button>
48
+ <Button
49
+ variant="contained"
50
+ color="primary"
51
+ onClick={() => {
52
+ handleClose()
53
+ }}
54
+ >
55
+ Cancel
56
+ </Button>
57
+ </DialogActions>
58
+ </Dialog>
59
+ </>
60
+ )
61
+ }
@@ -0,0 +1,36 @@
1
+ import { ConfigurationSchema } from '@jbrowse/core/configuration'
2
+ import { Instance } from 'mobx-state-tree'
3
+ import { BaseInternetAccountConfig } from '@jbrowse/core/pluggableElementTypes/models'
4
+
5
+ /**
6
+ * #config ExternalTokenInternetAccount
7
+ */
8
+ function x() {} // eslint-disable-line @typescript-eslint/no-unused-vars
9
+
10
+ const ExternalTokenConfigSchema = ConfigurationSchema(
11
+ 'ExternalTokenInternetAccount',
12
+ {
13
+ /**
14
+ * #slot
15
+ */
16
+ validateWithHEAD: {
17
+ description: 'validate the token with a HEAD request before using it',
18
+ type: 'boolean',
19
+ defaultValue: true,
20
+ },
21
+ },
22
+ {
23
+ /**
24
+ * #baseConfiguration
25
+ */
26
+ baseConfiguration: BaseInternetAccountConfig,
27
+ explicitlyTyped: true,
28
+ },
29
+ )
30
+
31
+ export type ExternalTokenInternetAccountConfigModel =
32
+ typeof ExternalTokenConfigSchema
33
+
34
+ export type ExternalTokenInternetAccountConfig =
35
+ Instance<ExternalTokenInternetAccountConfigModel>
36
+ export default ExternalTokenConfigSchema
@@ -0,0 +1,2 @@
1
+ export { default as configSchema } from './configSchema'
2
+ export { default as modelFactory } from './model'
@@ -0,0 +1,70 @@
1
+ import { ConfigurationReference, getConf } from '@jbrowse/core/configuration'
2
+ import { InternetAccount } from '@jbrowse/core/pluggableElementTypes/models'
3
+ import { UriLocation } from '@jbrowse/core/util/types'
4
+ import { ExternalTokenInternetAccountConfigModel } from './configSchema'
5
+ import { Instance, types, getRoot } from 'mobx-state-tree'
6
+
7
+ import { ExternalTokenEntryForm } from './ExternalTokenEntryForm'
8
+
9
+ const stateModelFactory = (
10
+ configSchema: ExternalTokenInternetAccountConfigModel,
11
+ ) => {
12
+ return InternetAccount.named('ExternalTokenInternetAccount')
13
+ .props({
14
+ type: types.literal('ExternalTokenInternetAccount'),
15
+ configuration: ConfigurationReference(configSchema),
16
+ })
17
+ .views(self => ({
18
+ get validateWithHEAD(): boolean {
19
+ return getConf(self, 'validateWithHEAD')
20
+ },
21
+ }))
22
+ .actions(self => ({
23
+ getTokenFromUser(
24
+ resolve: (token: string) => void,
25
+ reject: (error: Error) => void,
26
+ ) {
27
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
28
+ const { session } = getRoot<any>(self)
29
+ session.queueDialog((doneCallback: () => void) => [
30
+ ExternalTokenEntryForm,
31
+ {
32
+ internetAccountId: self.internetAccountId,
33
+ handleClose: (token: string) => {
34
+ if (token) {
35
+ resolve(token)
36
+ } else {
37
+ reject(new Error('user cancelled entry'))
38
+ }
39
+ doneCallback()
40
+ },
41
+ },
42
+ ])
43
+ },
44
+ async validateToken(token: string, location: UriLocation) {
45
+ if (!self.validateWithHEAD) {
46
+ return token
47
+ }
48
+ const newInit = self.addAuthHeaderToInit({ method: 'HEAD' }, token)
49
+ const response = await fetch(location.uri, newInit)
50
+ if (!response.ok) {
51
+ let errorMessage
52
+ try {
53
+ errorMessage = await response.text()
54
+ } catch (error) {
55
+ errorMessage = ''
56
+ }
57
+ throw new Error(
58
+ `Token could not be validated — ${response.status} (${
59
+ response.statusText
60
+ })${errorMessage ? ` (${errorMessage})` : ''}`,
61
+ )
62
+ }
63
+ return token
64
+ },
65
+ }))
66
+ }
67
+
68
+ export default stateModelFactory
69
+ export type ExternalTokenStateModel = ReturnType<typeof stateModelFactory>
70
+ export type ExternalTokenModel = Instance<ExternalTokenStateModel>
@@ -0,0 +1,61 @@
1
+ import { ConfigurationSchema } from '@jbrowse/core/configuration'
2
+ import { Instance } from 'mobx-state-tree'
3
+ import OAuthConfigSchema from '../OAuthModel/configSchema'
4
+
5
+ /**
6
+ * #config GoogleDriveOAuthInternetAccount
7
+ */
8
+ function x() {} // eslint-disable-line @typescript-eslint/no-unused-vars
9
+
10
+ const GoogleDriveOAuthConfigSchema = ConfigurationSchema(
11
+ 'GoogleDriveOAuthInternetAccount',
12
+ {
13
+ /**
14
+ * #slot
15
+ */
16
+ authEndpoint: {
17
+ description: 'the authorization code endpoint of the internet account',
18
+ type: 'string',
19
+ defaultValue: 'https://accounts.google.com/o/oauth2/v2/auth',
20
+ },
21
+ /**
22
+ * #slot
23
+ */
24
+ scopes: {
25
+ description: 'optional scopes for the authorization call',
26
+ type: 'string',
27
+ defaultValue: 'https://www.googleapis.com/auth/drive.readonly',
28
+ },
29
+ /**
30
+ * #slot
31
+ */
32
+ domains: {
33
+ description:
34
+ 'array of valid domains the url can contain to use this account',
35
+ type: 'stringArray',
36
+ defaultValue: ['drive.google.com"'],
37
+ },
38
+ /**
39
+ * #slot
40
+ */
41
+ responseType: {
42
+ description: 'the type of response from the authorization endpoint',
43
+ type: 'string',
44
+ defaultValue: 'token',
45
+ },
46
+ },
47
+ {
48
+ /**
49
+ * #baseConfiguration
50
+ */
51
+ baseConfiguration: OAuthConfigSchema,
52
+ explicitlyTyped: true,
53
+ },
54
+ )
55
+
56
+ export type GoogleDriveOAuthInternetAccountConfigModel =
57
+ typeof GoogleDriveOAuthConfigSchema
58
+
59
+ export type GoogleDriveOAuthInternetAccountConfig =
60
+ Instance<GoogleDriveOAuthInternetAccountConfigModel>
61
+ export default GoogleDriveOAuthConfigSchema
@@ -0,0 +1,2 @@
1
+ export { default as configSchema } from './configSchema'
2
+ export { default as modelFactory } from './model'
@@ -0,0 +1,174 @@
1
+ import React from 'react'
2
+ import { ConfigurationReference } from '@jbrowse/core/configuration'
3
+ import { Instance, types } from 'mobx-state-tree'
4
+ import { RemoteFileWithRangeCache } from '@jbrowse/core/util/io'
5
+ import { UriLocation } from '@jbrowse/core/util/types'
6
+ import { SvgIconProps, SvgIcon } from '@mui/material'
7
+ import {
8
+ FilehandleOptions,
9
+ Stats,
10
+ PolyfilledResponse,
11
+ } from 'generic-filehandle'
12
+
13
+ // locals
14
+ import { GoogleDriveOAuthInternetAccountConfigModel } from './configSchema'
15
+ import baseModel from '../OAuthModel/model'
16
+ import { configSchema as OAuthConfigSchema } from '../OAuthModel'
17
+
18
+ export interface RequestInitWithMetadata extends RequestInit {
19
+ metadataOnly?: boolean
20
+ }
21
+
22
+ interface GoogleDriveFilehandleOptions extends FilehandleOptions {
23
+ fetch(
24
+ input: RequestInfo,
25
+ opts?: RequestInitWithMetadata,
26
+ ): Promise<PolyfilledResponse>
27
+ }
28
+
29
+ interface GoogleDriveError {
30
+ error: {
31
+ errors: {
32
+ domain: string
33
+ reason: string
34
+ message: string
35
+ locationType?: string
36
+ location?: string
37
+ }[]
38
+ code: number
39
+ message: string
40
+ }
41
+ }
42
+
43
+ export class GoogleDriveFile extends RemoteFileWithRangeCache {
44
+ private statsPromise: Promise<{ size: number }>
45
+ constructor(source: string, opts: GoogleDriveFilehandleOptions) {
46
+ super(source, opts)
47
+ this.statsPromise = this.fetch(source, {
48
+ metadataOnly: true,
49
+ }).then((response: Response) => response.json())
50
+ }
51
+
52
+ async fetch(
53
+ input: RequestInfo,
54
+ opts?: RequestInitWithMetadata,
55
+ ): Promise<PolyfilledResponse> {
56
+ return super.fetch(input, opts)
57
+ }
58
+
59
+ async stat(): Promise<Stats> {
60
+ return this.statsPromise
61
+ }
62
+ }
63
+
64
+ function GoogleDriveIcon(props: SvgIconProps) {
65
+ return (
66
+ <SvgIcon {...props}>
67
+ <path d="M7.71,3.5L1.15,15L4.58,21L11.13,9.5M9.73,15L6.3,21H19.42L22.85,15M22.28,14L15.42,2H8.58L8.57,2L15.43,14H22.28Z" />
68
+ </SvgIcon>
69
+ )
70
+ }
71
+
72
+ async function getDescriptiveErrorMessage(response: Response) {
73
+ let errorMessage
74
+ try {
75
+ errorMessage = await response.text()
76
+ } catch (error) {
77
+ errorMessage = ''
78
+ }
79
+ if (errorMessage) {
80
+ let errorMessageParsed: GoogleDriveError | undefined
81
+ try {
82
+ errorMessageParsed = JSON.parse(errorMessage)
83
+ } catch (error) {
84
+ errorMessageParsed = undefined
85
+ }
86
+ if (errorMessageParsed) {
87
+ errorMessage = errorMessageParsed.error.message
88
+ }
89
+ }
90
+ return `Network response failure — ${response.status} (${
91
+ response.statusText
92
+ })${errorMessage ? ` (${errorMessage})` : ''}`
93
+ }
94
+
95
+ const stateModelFactory = (
96
+ configSchema: GoogleDriveOAuthInternetAccountConfigModel,
97
+ ) => {
98
+ return baseModel(OAuthConfigSchema)
99
+ .named('GoogleDriveOAuthInternetAccount')
100
+ .props({
101
+ type: types.literal('GoogleDriveOAuthInternetAccount'),
102
+ configuration: ConfigurationReference(configSchema),
103
+ })
104
+ .views(() => ({
105
+ get toggleContents() {
106
+ return <GoogleDriveIcon />
107
+ },
108
+ get selectorLabel() {
109
+ return 'Enter Google Drive share link'
110
+ },
111
+ }))
112
+ .actions(self => ({
113
+ getFetcher(
114
+ location?: UriLocation,
115
+ ): (input: RequestInfo, init?: RequestInit) => Promise<Response> {
116
+ return async (
117
+ input: RequestInfo,
118
+ init?: RequestInitWithMetadata,
119
+ ): Promise<Response> => {
120
+ const urlId = String(input).match(/[-\w]{25,}/)
121
+ const driveUrl = new URL(
122
+ `https://www.googleapis.com/drive/v3/files/${urlId}`,
123
+ )
124
+ const searchParams = new URLSearchParams()
125
+ if (init?.metadataOnly) {
126
+ searchParams.append('fields', 'size')
127
+ } else {
128
+ searchParams.append('alt', 'media')
129
+ }
130
+ driveUrl.search = searchParams.toString()
131
+ const authToken = await self.getToken(location)
132
+ const newInit = self.addAuthHeaderToInit(
133
+ { ...init, method: 'GET', credentials: 'same-origin' },
134
+ authToken,
135
+ )
136
+ const response = await fetch(driveUrl.toString(), newInit)
137
+ if (!response.ok) {
138
+ const message = await getDescriptiveErrorMessage(response)
139
+ throw new Error(message)
140
+ }
141
+ return response
142
+ }
143
+ },
144
+ openLocation(location: UriLocation) {
145
+ return new GoogleDriveFile(location.uri, {
146
+ fetch: this.getFetcher(location),
147
+ })
148
+ },
149
+ async validateToken(
150
+ token: string,
151
+ location: UriLocation,
152
+ ): Promise<string> {
153
+ const urlId = location.uri.match(/[-\w]{25,}/)
154
+ const response = await fetch(
155
+ `https://www.googleapis.com/drive/v3/files/${urlId}`,
156
+ {
157
+ headers: {
158
+ Authorization: `Bearer ${token}`,
159
+ 'Content-Type': 'application/x-www-form-urlencoded',
160
+ },
161
+ },
162
+ )
163
+ if (!response.ok) {
164
+ const message = await getDescriptiveErrorMessage(response)
165
+ throw new Error(`Token could not be validated. ${message}`)
166
+ }
167
+ return token
168
+ },
169
+ }))
170
+ }
171
+
172
+ export default stateModelFactory
173
+ export type GoogleDriveOAuthStateModel = ReturnType<typeof stateModelFactory>
174
+ export type GoogleDriveOAuthModel = Instance<GoogleDriveOAuthStateModel>
@@ -0,0 +1,71 @@
1
+ import React, { useState } from 'react'
2
+ import {
3
+ Button,
4
+ Dialog,
5
+ DialogContent,
6
+ DialogTitle,
7
+ DialogActions,
8
+ TextField,
9
+ } from '@mui/material'
10
+
11
+ export const HTTPBasicLoginForm = ({
12
+ internetAccountId,
13
+ handleClose,
14
+ }: {
15
+ internetAccountId: string
16
+ handleClose: (arg?: string) => void
17
+ }) => {
18
+ const [username, setUsername] = useState('')
19
+ const [password, setPassword] = useState('')
20
+
21
+ function onSubmit(event: React.FormEvent<HTMLFormElement>) {
22
+ if (username && password) {
23
+ handleClose(btoa(`${username}:${password}`))
24
+ } else {
25
+ handleClose()
26
+ }
27
+ event.preventDefault()
28
+ }
29
+
30
+ return (
31
+ <>
32
+ <Dialog open maxWidth="xl" data-testid="login-httpbasic">
33
+ <DialogTitle>Log In for {internetAccountId}</DialogTitle>
34
+ <form onSubmit={onSubmit}>
35
+ <DialogContent style={{ display: 'flex', flexDirection: 'column' }}>
36
+ <TextField
37
+ required
38
+ label="Username"
39
+ variant="outlined"
40
+ inputProps={{ 'data-testid': 'login-httpbasic-username' }}
41
+ onChange={event => setUsername(event.target.value)}
42
+ margin="dense"
43
+ />
44
+ <TextField
45
+ required
46
+ label="Password"
47
+ type="password"
48
+ autoComplete="current-password"
49
+ variant="outlined"
50
+ inputProps={{ 'data-testid': 'login-httpbasic-password' }}
51
+ onChange={event => setPassword(event.target.value)}
52
+ margin="dense"
53
+ />
54
+ </DialogContent>
55
+ <DialogActions>
56
+ <Button variant="contained" color="primary" type="submit">
57
+ Submit
58
+ </Button>
59
+ <Button
60
+ variant="contained"
61
+ type="submit"
62
+ onClick={() => handleClose()}
63
+ >
64
+ Cancel
65
+ </Button>
66
+ </DialogActions>
67
+ </form>
68
+ </Dialog>
69
+ </>
70
+ )
71
+ }
@@ -0,0 +1,43 @@
1
+ import { ConfigurationSchema } from '@jbrowse/core/configuration'
2
+ import { Instance } from 'mobx-state-tree'
3
+ import { BaseInternetAccountConfig } from '@jbrowse/core/pluggableElementTypes/models'
4
+
5
+ /**
6
+ * #config HTTPBasicInternetAccount
7
+ */
8
+ function x() {} // eslint-disable-line @typescript-eslint/no-unused-vars
9
+
10
+ const HTTPBasicConfigSchema = ConfigurationSchema(
11
+ 'HTTPBasicInternetAccount',
12
+ {
13
+ /**
14
+ * #slot
15
+ */
16
+ tokenType: {
17
+ description: 'a custom name for a token to include in the header',
18
+ type: 'string',
19
+ defaultValue: 'Basic',
20
+ },
21
+ /**
22
+ * #slot
23
+ */
24
+ validateWithHEAD: {
25
+ description: 'validate the token with a HEAD request before using it',
26
+ type: 'boolean',
27
+ defaultValue: true,
28
+ },
29
+ },
30
+ {
31
+ /**
32
+ * #baseConfiguration
33
+ */
34
+ baseConfiguration: BaseInternetAccountConfig,
35
+ explicitlyTyped: true,
36
+ },
37
+ )
38
+
39
+ export type HTTPBasicInternetAccountConfigModel = typeof HTTPBasicConfigSchema
40
+
41
+ export type HTTPBasicInternetAccountConfig =
42
+ Instance<HTTPBasicInternetAccountConfigModel>
43
+ export default HTTPBasicConfigSchema
@@ -0,0 +1,2 @@
1
+ export { default as configSchema } from './configSchema'
2
+ export { default as modelFactory } from './model'