Merge branch '12377-arvbox-composer' refs #12377
[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         save: function(k, v) {
32             var sessions = db.loadAll()
33             sessions[k] = v
34             Object.keys(sessions).forEach(function(key) {
35                 if (sessions[key].isFromRails)
36                     delete sessions[key]
37             })
38             window.localStorage.setItem('sessions', JSON.stringify(sessions))
39         },
40         trash: function(k) {
41             var sessions = db.loadAll()
42             delete sessions[k]
43             window.localStorage.setItem('sessions', JSON.stringify(sessions))
44         },
45         findAPI: function(url) {
46             // Given a Workbench or API host or URL, return a promise
47             // for the corresponding API server's base URL.  Typical
48             // use:
49             // sessionDB.findAPI('https://workbench.example/foo').then(sessionDB.login)
50             if (url.indexOf('://') < 0)
51                 url = 'https://' + url
52             url = new URL(url)
53             return m.request(url.origin + '/discovery/v1/apis/arvados/v1/rest').then(function() {
54                 return url.origin + '/'
55             }).catch(function(err) {
56                 // If url is a Workbench site (and isn't too old),
57                 // /status.json will tell us its API host.
58                 return m.request(url.origin + '/status.json').then(function(resp) {
59                     if (!resp.apiBaseURL)
60                         throw 'no apiBaseURL in status response'
61                     return resp.apiBaseURL
62                 })
63             })
64         },
65         login: function(baseURL) {
66             // Initiate login procedure with given API base URL (e.g.,
67             // "http://api.example/").
68             //
69             // Any page that has a button that invokes login() must
70             // also call checkForNewToken() on (at least) its first
71             // render. Otherwise, the login procedure can't be
72             // completed.
73             document.location = baseURL + 'login?return_to=' + encodeURIComponent(document.location.href.replace(/\?.*/, '')+'?baseURL='+encodeURIComponent(baseURL))
74             return false
75         },
76         logout: function(k) {
77             // Forget the token, but leave the other info in the db so
78             // the user can log in again without providing the login
79             // host again.
80             var sessions = db.loadAll()
81             delete sessions[k].token
82             db.save(k, sessions[k])
83         },
84         checkForNewToken: function() {
85             // If there's a token and baseURL in the location bar (i.e.,
86             // we just landed here after a successful login), save it and
87             // scrub the location bar.
88             if (document.location.search[0] != '?')
89                 return
90             var params = {}
91             document.location.search.slice(1).split('&').map(function(kv) {
92                 var e = kv.indexOf('=')
93                 if (e < 0)
94                     return
95                 params[decodeURIComponent(kv.slice(0, e))] = decodeURIComponent(kv.slice(e+1))
96             })
97             if (!params.baseURL || !params.api_token)
98                 // Have a query string, but it's not a login callback.
99                 return
100             params.token = params.api_token
101             delete params.api_token
102             db.save(params.baseURL, params)
103             history.replaceState({}, '', document.location.origin + document.location.pathname)
104         },
105         fillMissingUUIDs: function() {
106             var sessions = db.loadAll()
107             Object.keys(sessions).map(function(key) {
108                 if (key.indexOf('://') < 0)
109                     return
110                 // key is the baseURL placeholder. We need to get our user
111                 // record to find out the cluster's real uuid prefix.
112                 var session = sessions[key]
113                 m.request(session.baseURL+'arvados/v1/users/current', {
114                     headers: {
115                         authorization: 'OAuth2 '+session.token,
116                     },
117                 }).then(function(user) {
118                     session.user = user
119                     db.save(user.uuid.slice(0, 5), session)
120                     db.trash(key)
121                 })
122             })
123         },
124         // Return the Workbench base URL advertised by the session's
125         // API server, or a reasonable guess, or (if neither strategy
126         // works out) null.
127         workbenchBaseURL: function(session) {
128             var dd = db.discoveryDoc(session)()
129             if (!dd)
130                 // Don't fall back to guessing until we receive the discovery doc
131                 return null
132             if (dd.workbenchUrl)
133                 return dd.workbenchUrl
134             // Guess workbench.{apihostport} is a Workbench... unless
135             // the host part of apihostport is an IPv4 or [IPv6]
136             // address.
137             if (!session.baseURL.match('://(\\[|\\d+\\.\\d+\\.\\d+\\.\\d+[:/])'))
138                 return session.baseURL.replace('://', '://workbench.')
139             return null
140         },
141         // Return a m.stream that will get fulfilled with the
142         // discovery doc from a session's API server.
143         discoveryDoc: function(session) {
144             var cache = db.discoveryCache[session.baseURL]
145             if (!cache) {
146                 db.discoveryCache[session.baseURL] = cache = m.stream()
147                 m.request(session.baseURL+'discovery/v1/apis/arvados/v1/rest').then(cache)
148             }
149             return cache
150         },
151         request: function(session, path, opts) {
152             opts = opts || {}
153             opts.headers = opts.headers || {}
154             opts.headers.authorization = 'OAuth2 '+ session.token
155             return m.request(session.baseURL + path, opts)
156         },
157     })
158 }