12479: Merge branch 'master' into 12479-wb-structured-vocabulary
[arvados.git] / apps / workbench / app / assets / javascripts / models / session_db.js
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 window.SessionDB = function() {
6     var db = this
7     Object.assign(db, {
8         discoveryCache: {},
9         loadFromLocalStorage: function() {
10             try {
11                 return JSON.parse(window.localStorage.getItem('sessions')) || {}
12             } catch(e) {}
13             return {}
14         },
15         loadAll: function() {
16             var all = db.loadFromLocalStorage()
17             if (window.defaultSession) {
18                 window.defaultSession.isFromRails = true
19                 all[window.defaultSession.user.uuid.slice(0, 5)] = window.defaultSession
20             }
21             return all
22         },
23         loadActive: function() {
24             var sessions = db.loadAll()
25             Object.keys(sessions).forEach(function(key) {
26                 if (!sessions[key].token)
27                     delete sessions[key]
28             })
29             return sessions
30         },
31         loadLocal: function() {
32             var sessions = db.loadActive()
33             var s = false
34             Object.values(sessions).forEach(function(session) {
35                 if (session.isFromRails) {
36                     s = session
37                     return
38                 }
39             })
40             return s
41         },
42         save: function(k, v) {
43             var sessions = db.loadAll()
44             sessions[k] = v
45             Object.keys(sessions).forEach(function(key) {
46                 if (sessions[key].isFromRails)
47                     delete sessions[key]
48             })
49             window.localStorage.setItem('sessions', JSON.stringify(sessions))
50         },
51         trash: function(k) {
52             var sessions = db.loadAll()
53             delete sessions[k]
54             window.localStorage.setItem('sessions', JSON.stringify(sessions))
55         },
56         findAPI: function(url) {
57             // Given a Workbench or API host or URL, return a promise
58             // for the corresponding API server's base URL.  Typical
59             // use:
60             // sessionDB.findAPI('https://workbench.example/foo').then(sessionDB.login)
61             if (url.indexOf('://') < 0)
62                 url = 'https://' + url
63             url = new URL(url)
64             return m.request(url.origin + '/discovery/v1/apis/arvados/v1/rest').then(function() {
65                 return url.origin + '/'
66             }).catch(function(err) {
67                 // If url is a Workbench site (and isn't too old),
68                 // /status.json will tell us its API host.
69                 return m.request(url.origin + '/status.json').then(function(resp) {
70                     if (!resp.apiBaseURL)
71                         throw 'no apiBaseURL in status response'
72                     return resp.apiBaseURL
73                 })
74             })
75         },
76         login: function(baseURL) {
77             // Initiate login procedure with given API base URL (e.g.,
78             // "http://api.example/").
79             //
80             // Any page that has a button that invokes login() must
81             // also call checkForNewToken() on (at least) its first
82             // render. Otherwise, the login procedure can't be
83             // completed.
84             document.location = baseURL + 'login?return_to=' + encodeURIComponent(document.location.href.replace(/\?.*/, '')+'?baseURL='+encodeURIComponent(baseURL))
85             return false
86         },
87         logout: function(k) {
88             // Forget the token, but leave the other info in the db so
89             // the user can log in again without providing the login
90             // host again.
91             var sessions = db.loadAll()
92             delete sessions[k].token
93             db.save(k, sessions[k])
94         },
95         checkForNewToken: function() {
96             // If there's a token and baseURL in the location bar (i.e.,
97             // we just landed here after a successful login), save it and
98             // scrub the location bar.
99             if (document.location.search[0] != '?')
100                 return
101             var params = {}
102             document.location.search.slice(1).split('&').map(function(kv) {
103                 var e = kv.indexOf('=')
104                 if (e < 0)
105                     return
106                 params[decodeURIComponent(kv.slice(0, e))] = decodeURIComponent(kv.slice(e+1))
107             })
108             if (!params.baseURL || !params.api_token)
109                 // Have a query string, but it's not a login callback.
110                 return
111             params.token = params.api_token
112             delete params.api_token
113             db.save(params.baseURL, params)
114             history.replaceState({}, '', document.location.origin + document.location.pathname)
115         },
116         fillMissingUUIDs: function() {
117             var sessions = db.loadAll()
118             Object.keys(sessions).map(function(key) {
119                 if (key.indexOf('://') < 0)
120                     return
121                 // key is the baseURL placeholder. We need to get our user
122                 // record to find out the cluster's real uuid prefix.
123                 var session = sessions[key]
124                 m.request(session.baseURL+'arvados/v1/users/current', {
125                     headers: {
126                         authorization: 'OAuth2 '+session.token,
127                     },
128                 }).then(function(user) {
129                     session.user = user
130                     db.save(user.uuid.slice(0, 5), session)
131                     db.trash(key)
132                 })
133             })
134         },
135         // Return the Workbench base URL advertised by the session's
136         // API server, or a reasonable guess, or (if neither strategy
137         // works out) null.
138         workbenchBaseURL: function(session) {
139             var dd = db.discoveryDoc(session)()
140             if (!dd)
141                 // Don't fall back to guessing until we receive the discovery doc
142                 return null
143             if (dd.workbenchUrl)
144                 return dd.workbenchUrl
145             // Guess workbench.{apihostport} is a Workbench... unless
146             // the host part of apihostport is an IPv4 or [IPv6]
147             // address.
148             if (!session.baseURL.match('://(\\[|\\d+\\.\\d+\\.\\d+\\.\\d+[:/])')) {
149                 var wbUrl = session.baseURL.replace('://', '://workbench.')
150                 // Remove the trailing slash, if it's there.
151                 return wbUrl.slice(-1) == '/' ? wbUrl.slice(0, -1) : wbUrl
152             }
153             return null
154         },
155         // Return a m.stream that will get fulfilled with the
156         // discovery doc from a session's API server.
157         discoveryDoc: function(session) {
158             var cache = db.discoveryCache[session.baseURL]
159             if (!cache) {
160                 db.discoveryCache[session.baseURL] = cache = m.stream()
161                 m.request(session.baseURL+'discovery/v1/apis/arvados/v1/rest').then(cache)
162             }
163             return cache
164         },
165         request: function(session, path, opts) {
166             opts = opts || {}
167             opts.headers = opts.headers || {}
168             opts.headers.authorization = 'OAuth2 '+ session.token
169             return m.request(session.baseURL + path, opts)
170         },
171     })
172 }