/
0057_comphound_auth.html
454 lines (400 loc) · 18.5 KB
/
0057_comphound_auth.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
<p><head>
<title>The 3D Web Coder</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<link rel="stylesheet" type="text/css" href="3dwc.css"/>
<script src="run_prettify.js" type="text/javascript"></script>
<!--
<script src="https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js" type="text/javascript"></script>
-->
</head></p>
<!---
#adskdevnetwrk
#expressjs
#RestSharp
#Autodesk #IoT #SeeControl #cloud
#python #markdown #asciidoc
#gcal #caldav #googleapi
#milanojs
#3dwebaccel #prague #webgl #3dweb #a360
#au2015 #autocad #inventor #ah8 #cubeathens #developers
#aws #handlebars
#javascript
#JsFiddle #Reactjs
#autodesku #rtceur
#Reactjs
#MongoDB
#mongolab #jquery
akn_include
CompHound Viewer and Authorisation Service #Heroku #3dwebcoder #revitapi #nodejs #adsk #ViewAndDataAPI #restapi
I continued working on my CompHound component tracker and now successfully integrated the viewer
– CompHound updates and the LMV viewer
– Authorisation token server
– LMV consumer key and secret environment variables
– Client-side auth code provider
– Viewer set-up
– Wrap-up and next steps...
-->
<h3>CompHound Viewer and Authorisation Service</h3>
<p>I continued working on my <a href="https://github.com/CompHound">CompHound</a> component tracker and now successfully integrated the viewer, just in time to wrap up before leaving for two weeks vacation starting next Monday.</p>
<ul>
<li><a href="#2">CompHound updates and the LMV viewer</a></li>
<li><a href="#3">Authorisation token server</a></li>
<li><a href="#3.1">LMV consumer key and secret environment variables</a></li>
<li><a href="#4">Client-side auth code provider</a></li>
<li><a href="#5">Viewer set-up</a></li>
<li><a href="#6">Wrap-up and next steps</a></li>
</ul>
<h4><a name="2"></a>CompHound Updates and the LMV Viewer</h4>
<p>I finally gave up resisting the pernicious acronym LMV, short for <em>large model viewer</em>.</p>
<p>It has always been used as an unofficial internal code name for the Autodesk
<a href="https://developer.autodesk.com">View and Data API</a> viewer.</p>
<p>No other abbreviation had any success yet, and many colleagues are using LMV, so I'll join the ranks.</p>
<p>Before starting to work on viewer stuff, I fiddled with setting up two columns in the CompHound user interface for the selected component occurrence data and the viewer, but did not complete that yet.</p>
<p>I also consolidated and cleaned up of the three readme files, so that now
the <a href="https://github.com/CompHound/CompHoundWeb">web server</a>
and <a href="https://github.com/CompHound/CompHoundRvt">C# client</a> ones
both point clearly straight to
the <a href="https://github.com/CompHound/CompHound.github.io">landing page</a>.</p>
<p>The viewer component uses OAuth 2 authorisation, so it consists of two parts:</p>
<ul>
<li>Authorisation token server</li>
<li>Client-side token access and viewer</li>
</ul>
<p>On the client side of things, a separate little JavaScript object helps access the authorisation tokens provided by the server.</p>
<p>I will therefore discuss the following three implementation files:</p>
<ul>
<li>/lmvauth/AuthTokenServer.js – the <a href="#3">authorisation token server</a></li>
<li>/public/js/lmvauthtoken.js – the <a href="#4">client-side auth code provider</a></li>
<li>/public/js/lmviewer.js – the <a href="#5">viewer set-up</a></li>
</ul>
<p>The latter two are public and pulled in by the user interface defined in <code>public/datatable2.html</code>.</p>
<h4><a name="3"></a>Authorisation Token Server</h4>
<p>I implemented the authorisation server and new REST API routes for it based on
<a href="https://github.com/JimAwe">Jim Awe</a>'s
<a href="https://github.com/Developer-Autodesk/AuthTokenServer_Simple">simple AuthTokenServer using Node.js</a>.</p>
<p>Its functionality is provided by the <code>LmvAuthorisationService</code> defined in <code>./lmvauth/AuthTokenServer.js</code>:</p>
<pre class="prettyprint">
// AuthTokenServer.js
// by Jim Awe
var https = require("https");
// Call the Autodesk authentication API to get a token
// based on our client_id and client_secret.
// When we get a response, forward that response on to
// the browser-based app that called us needing the token.
function getAuthCode(mainResponse, baseUrl,
clientId, clientSecret)
{
var dataString =
"client_id=" + clientId
+ "&client_secret=" + clientSecret
+ "&grant_type=client_credentials";
var headers = {
"Content-Type": "application/x-www-form-urlencoded"
};
var options = {
host: baseUrl,
port: 443,
path: "/authentication/v1/authenticate",
method: "POST",
headers: headers,
// only for dev!
rejectUnauthorized: false,
requestCert: true,
agent: false
};
var req = https.request(options, function(res) {
res.setEncoding("utf8");
var responseString = "";
res.on("data", function (data) {
responseString += data;
});
res.on("end", function() {
console.log(responseString);
mainResponse.setHeader('Content-Type', 'application/json');
mainResponse.setHeader('Access-Control-Allow-Origin', '*');
mainResponse.send(responseString); // forward response on to the original call from the browser app
});
});
req.write(dataString);
req.end();
}
// Define entry points for the URLs the browser based
// app will send to us to get the token. Send one
// appropriate for the given environment. If you only
// have keys for the PRODUCTION environment, then just
// replace those below, otherwise you can replace them
// all and easily switch environments from your browser
// app.
LmvAuthorisationService = {
auth : function(req, res) {
console.log("AuthTokenServer: getting PRODUCTION token...");
// ***** PUT YOUR PRODUCTION KEYS HERE *****
getAuthCode(res, "developer.api.autodesk.com",
process.env.COMPHOUND_CONSUMERKEY,
process.env.COMPHOUND_CONSUMERSECRET);
},
authstg : function(req, res) {
console.log("AuthTokenServer: getting STAGING token...");
// ***** PUT YOUR STAGING KEYS HERE *****
getAuthCode(res, "developer-stg.api.autodesk.com",
process.env.COMPHOUND_CONSUMERKEY,
process.env.COMPHOUND_CONSUMERSECRET);
},
authdev : function(req, res) {
// need endpoint and keys for DEV
console.log("AuthTokenServer: getting DEV token...");
// ***** PUT YOUR DEV KEYS HERE *****
getAuthCode(res, "developer-dev.api.autodesk.com",
process.env.COMPHOUND_CONSUMERKEY,
process.env.COMPHOUND_CONSUMERSECRET);
},
authtest : function(req, res) {
res.send("LmvAuthorisationService: I'm alive!");
}
}
module.exports = LmvAuthorisationService;
</pre>
<p>This functionality is made available via the last four public REST API routes defined in <code>routes.js</code>:</p>
<pre class="prettyprint">
module.exports = function(app) {
var InstanceService = require('./controller/instances_v1');
app.get('/api/v1/instances', InstanceService.findAll);
app.get('/api/v1/instances/:id', InstanceService.findById);
app.post('/api/v1/instances', InstanceService.add);
app.put('/api/v1/instances/:id', InstanceService.update4);
app.delete('/api/v1/instances/:id', InstanceService.delete);
app.get('/api/v1/instances/project/:pid', InstanceService.findAllForProject);
var LmvAuthorisationService = require('./lmvauth/AuthTokenServer');
app.get('/api/v1/auth', LmvAuthorisationService.auth);
app.get('/api/v1/auth-stg', LmvAuthorisationService.authstg);
app.get('/api/v1/auth-dev', LmvAuthorisationService.authdev);
app.get('/api/v1/auth-test', LmvAuthorisationService.authtest);
}
</pre>
<h4><a name="3.1"></a>LMV Consumer Key and Secret Environment Variables</h4>
<p>To avoid publishing my View and Data API consumer key and secret, I store them on my local system in environment variables named
<code>COMPHOUND_CONSUMERKEY</code> and
<code>COMPHOUND_CONSUMERSECRET</code>.</p>
<pre class="prettyprint">
$ set | grep COMPH
COMPHOUND_CONSUMERKEY=pLdXXXXXXXXIfXXXXXXXXXXhzXXXXUeZ
COMPHOUND_CONSUMERSECRET=ReXXXXXXXXXXXXur
</pre>
<p>I added the following assertion right up front in <code>server.js</code> to ensure that nobody can forget to set them:</p>
<pre class="prettyprint">
// Ensure that the View and Data API consumer
// key and secret environment variables are set.
if( !process.env.COMPHOUND_CONSUMERKEY
|| !process.env.COMPHOUND_CONSUMERSECRET ) {
var msg = 'Please set the CompHound View and Data API '
+ 'consumer key and secret environment variables '
+ 'COMPHOUND_CONSUMERKEY and COMPHOUND_CONSUMERSECRET '
+ 'before starting the server.';
console.log( msg );
throw new Error( msg );
}
</pre>
<p>After redeploying the new version to Heroku, it crashed.</p>
<p>Checking the log files reveals the problem:</p>
<pre>
2015-09-30T18:57:10.941716+00:00 heroku[web.1]: State changed from up to down
2015-09-30T18:57:14.297264+00:00 heroku[web.1]: Stopping all processes with SIGTERM
2015-09-30T18:57:15.875941+00:00 heroku[web.1]: Process exited with status 143
2015-09-30T21:46:33.743366+00:00 heroku[api]: Deploy abd323f by jeremy.tammik@eur.autodesk.com
2015-09-30T21:46:33.743366+00:00 heroku[api]: Release v24 created by jeremy.tammik@eur.autodesk.com
2015-09-30T21:46:33.812780+00:00 heroku[slug-compiler]: Slug compilation started
2015-09-30T21:46:33.812797+00:00 heroku[slug-compiler]: Slug compilation finished
2015-09-30T21:47:12.357950+00:00 heroku[web.1]: Unidling
2015-09-30T21:47:12.358473+00:00 heroku[web.1]: State changed from down to starting
2015-09-30T21:47:20.524667+00:00 heroku[web.1]: Starting process with command `node server.js`
2015-09-30T21:47:22.405906+00:00 app[web.1]: Please set the CompHound View and Data API consumer key and secret environment variables COMPHOUND_CONSUMERKEY and COMPHOUND_CONSUMERSECRET before starting the server.
2015-09-30T21:47:22.407178+00:00 app[web.1]: /app/server.js:22
2015-09-30T21:47:22.406864+00:00 app[web.1]:
2015-09-30T21:47:22.407461+00:00 app[web.1]: throw new Error( msg );
2015-09-30T21:47:22.407467+00:00 app[web.1]: ^
2015-09-30T21:47:22.410027+00:00 app[web.1]: Error: Please set the CompHound View and Data API consumer key and secret environment variables COMPHOUND_CONSUMERKEY and COMPHOUND_CONSUMERSECRET before starting the server.
2015-09-30T21:47:22.410030+00:00 app[web.1]: at Object.<anonymous> (/app/server.js:22:9)
</pre>
<p>I forgot to set the environment variables on the Heroku server instance :-)</p>
<p>Apparently, my assertion works well.</p>
<p>How clever of me to build in a reminder – for myself :-)</p>
<p>Accordingly, I added the two configuration variables to Heroku:</p>
<p><center>
<img src="img/heroku_comphound_config_vars.png" alt="Heroku environment variables" width="674"/>
</center></p>
<p>Once that was done, all was well.</p>
<p>You can try out the authorisation token generation live by invoking
this <a href="https://comphound.herokuapp.com/api/v1/auth">link to the <code>/api/v1/auth</code> route</a>.</p>
<h4><a name="4"></a>Client-side Auth Code Provider</h4>
<p>Jim's <a href="https://github.com/Developer-Autodesk/AuthTokenServer_Simple">simple AuthTokenServer</a> also
includes a client-side helper module,
<a href="https://github.com/Developer-Autodesk/AuthTokenServer_Simple/blob/master/MyAuthToken.js">MyAuthToken.js</a>,
to access and provide the authorisation tokens generated on the server side and make them available to the JavaScript client.</p>
<p>I used his code to implement my own version in <code>/public/js/lmvauthtoken.js</code>, defining a JavaScript object <code>LmvAuthToken</code>:</p>
<pre class="prettyprint">
// lmvauthtoken.js
//
// Object to encapsulate retrieval of an authorization
// code for the viewing service. After declaring a
// global instance, you can repeatedly call value()
// whenever you need the token to pass to an API call.
// It will keep track of the expiration of the token
// and referesh it when necessary.
//
// NOTE: there is another way to accomplish this by just
// calling the API function with a token without worrying
// about whether it has expired, and then if it returns
// 'Invalid Token', then get a new token and retry.
// This is possible with jQuery, but only works with the
// .success()/.error() constructs and not with .done(),
// .fail() (at least not without a lot of convoluted extra
// work). For now, I am happier doing it this way, but am
// open to suggestions on best practices.
//
// Jim Awe
// Autodesk, Inc.
// CONS LmvAuthToken():
// locally running token service (Token Service is started
// with Node.js command: 'node AuthTokenServer.js'). If you
// deploy AuthTokenServer.js, this obj constructor needs to
// change URL accordingly.
function LmvAuthToken(env)
{
// Determine URL from window.location.
//Later: no need at all, just use the API route.
// http://stackoverflow.com/questions/1034621/get-current-url-in-web-browser
//var currentLocation = window.location;
var url = '/api/v1/';
if (env === 'PROD') { url += 'auth'; }
else if (env === 'STG') { url += 'auth-stg'; }
else if (env === 'DEV') { url += 'auth-dev'; }
else {
alert('DEVELOPER ERROR: No valid environment set for LmvAuthToken()');
}
console.log('LmvAuthToken url ' + url );
this.tokenService = url;
this.token = '';
this.expires_in = 0;
this.timestamp = 0;
}
// FUNC value():
// return the value of the token
LmvAuthToken.prototype.value = function()
{
// if we've never retrieved it, do it the first time
if (this.token === '') {
console.log('AUTH TOKEN: Getting for first time...');
this.get();
}
else {
// get current timestamp and see if we've expired yet
var curTimestamp = Math.round(new Date() / 1000); // time in seconds
var secsElapsed = curTimestamp - this.timestamp;
if (secsElapsed > (this.expires_in - 10)) {
// if we are within 10 secs of expiring, get new token
console.log('AUTH TOKEN: expired, refreshing...');
this.get();
}
else {
var secsLeft = this.expires_in - secsElapsed;
console.log('AUTH TOKEN: still valid (' + secsLeft + ' secs)');
}
}
return this.token;
};
// FUNC get():
// get the token from the Authentication service and
// cache it, along with the expiration time
LmvAuthToken.prototype.get = function()
{
var retVal = '';
var expires_in = 0;
var jqxhr = $.ajax({
url: this.tokenService,
type: 'GET',
async: false,
success: function(ajax_data) {
console.log('AUTH TOKEN: ' + ajax_data.access_token);
retVal = ajax_data.access_token; // NOTE: this only works because we've made the ajax call Synchronous (and 'this' is not valid in this scope!)
expires_in = ajax_data.expires_in;
},
error: function(jqXHR, textStatus) {
alert('AUTH TOKEN: Failed to get new auth token!');
}
});
this.token = retVal;
this.expires_in = expires_in;
this.timestamp = Math.round(new Date() / 1000); // get time in seconds when we retrieved this token
};
</pre>
<h4><a name="5"></a>Viewer Set-up</h4>
<p>In the HTML file, the viewer requires nothing but a <code>div</code> tag with an <code>id</code> attribute, e.g., <code>viewer</code>, in our case.</p>
<p>The <code>div</code> is populated by <code>public/js/lmviewer.js</code> as follows, also showing how to access the auth token provided by the <code>LmvAuthToken</code> object:</p>
<pre class="prettyprint">
// Change the token and urn (translated file location) before running.
//var token = 'TB4KC708xPeHYKxDBerbc850MOsS';
var lmvAuthToken = new LmvAuthToken('PROD');
var urn_little_house = 'dXJuOmFkc2sub2JqZWN0czpvcy5v...';
var urn_rst_advanced_sample_project = 'dXJuOmFkc2sub...';
var urn = urn_rst_advanced_sample_project;
function getToken() {
//return token;
return lmvAuthToken.value();
}
function lmv_loadDocument(viewer, documentId) {
// Find the first 3d geometry and load that.
Autodesk.Viewing.Document.load(documentId, function(doc) {
var geometryItems = Autodesk.Viewing.Document.getSubItemsWithProperties(
doc.getRootItem(),
{ 'type' : 'geometry', 'role' : '3d' },
true);
if (geometryItems.length > 0) {
viewer.load(doc.getViewablePath(geometryItems[0]));
}
},
function(errorMsg) { // onErrorCallback
alert('Load Error: ' + errorMsg);
});
}
function lmv_initialize() {
var options = {
'document' : 'urn:' + urn,
'env':'AutodeskProduction',
'getAccessToken': getToken,
'refreshToken': getToken };
var viewerElement = document.getElementById('viewer');
var viewer = new Autodesk.Viewing.Viewer3D(viewerElement, {});
Autodesk.Viewing.Initializer(options,function() {
viewer.initialize();
lmv_loadDocument(viewer, options.document);
});
}
</pre>
<p>The model URN is currently hard-coded, for testing purposes, to the <code>rst_advanced_sample_project.rvt</code> sample model that I also used to populate the database.</p>
<p>Later, we will use the <code>urn</code>
data <a href="http://the3dwebcoder.typepad.com/blog/2015/09/lunar-eclipse-cors-workaround-comphound-update.html#4">added to each component occurrence database record</a>.</p>
<h4><a name="6"></a>Wrap-up and Next Steps</h4>
<p>All the source code is available from the <a href="https://github.com/CompHound">CompHound</a> organisation repositories.
The current versions discussed above are the web
server <a href="https://github.com/CompHound/CompHoundWeb/releases/tag/0.0.28">CompHoundWeb 0.0.28</a> and
the C# REST API
client <a href="https://github.com/CompHound/CompHoundRvt/releases/tag/2016.0.0.5">CompHoundRvt 2016.0.0.5</a>.</p>
<p>I just redeployed it to Heroku again.
As always, the links
to <a href="https://github.com/CompHound/CompHound.github.io#try-it-out-live">Try it out Live</a> are
up and running, so feel free to play.</p>
<p>Please be aware that the user interface presented by the main entry
point <a href="https://comphound.herokuapp.com/datatable2">comphound.herokuapp.com/datatable2</a> is
rather messed up right now, because I have not yet implemented proper positioning for the LMV <code>div</code> tag.</p>
<p>I can remove some items from the to-do list, only to add other new ones:</p>
<ul>
<li>Implement automated LMV model translation.</li>
<li>Implement isolated viewing of an individual selected instance.</li>
<li>Set up and reroute the <a href="http://comphound.net">comphound.net</a> domain.</li>
<li>Enable editing some component occurrence properties.</li>
<li>Implement database population REST clients for other CAD systems, e.g.,
<a href="http://www.autodesk.com/products/autocad">AutoCAD</a>,
<a href="http://www.autodesk.com/products/inventor">Inventor</a>,
<a href="http://freecadweb.org">FreeCAD</a>, etc.</li>
<li>Vacation!</li>
</ul>