回答編集履歴

5

テキスト追加

2021/12/07 15:58

投稿

退会済みユーザー
test CHANGED
@@ -384,7 +384,7 @@
384
384
 
385
385
 
386
386
 
387
- `inputHtml`を修正して、保存されている名前を引数`name`で受け取り、それが
387
+ `inputHtml`を修正して、名前を引数`name`で受け取り、それが
388
388
 
389
389
  - undefined(などfalsy)のときには名前を入力できるtype="text"のinputとし
390
390
 

4

テキスト追加

2021/12/07 15:58

投稿

退会済みユーザー
test CHANGED
@@ -330,10 +330,216 @@
330
330
 
331
331
  ```javascript
332
332
 
333
- 'use strict';function _0x1d81(_0x6942b4,_0x34924a){const _0x1e71e1=_0x1e71();return _0x1d81=function(_0x1d8116,_0x127fa8){_0x1d8116=_0x1d8116-0x82;let _0x1f92bb=_0x1e71e1[_0x1d8116];return _0x1f92bb;},_0x1d81(_0x6942b4,_0x34924a);}const _0x2b7280=_0x1d81;(function(_0x4c1d23,_0x555af9){const _0x486365=_0x1d81,_0x1f02da=_0x4c1d23();while(!![]){try{const _0x57dff9=parseInt(_0x486365(0x97))/0x1+parseInt(_0x486365(0x96))/0x2+-parseInt(_0x486365(0x8c))/0x3*(-parseInt(_0x486365(0xa7))/0x4)+parseInt(_0x486365(0xa9))/0x5*(parseInt(_0x486365(0x91))/0x6)+-parseInt(_0x486365(0x9c))/0x7*(parseInt(_0x486365(0x9d))/0x8)+-parseInt(_0x486365(0x93))/0x9+parseInt(_0x486365(0x87))/0xa;if(_0x57dff9===_0x555af9)break;else _0x1f02da['push'](_0x1f02da['shift']());}catch(_0x1e20ea){_0x1f02da['push'](_0x1f02da['shift']());}}}(_0x1e71,0x62759));const DATE_FORMAT=_0x2b7280(0x8f);window[_0x2b7280(0xa5)]=function(){const _0x5be771=_0x2b7280,_0x22baa5=document[_0x5be771(0x98)](_0x5be771(0x85)),_0x174188='<p>名前を入力して今日のユーザコードを取得してください。</p>\x0a\x20\x20\x20\x20<p>\x0a\x20\x20\x20\x20\x20\x20<input\x20type=\x22text\x22\x20id=\x22your-name\x22\x20size=\x2230\x22\x20value=\x22山田太郎\x22>\x0a\x20\x20\x20\x20\x20\x20<button\x20type=\x22button\x22\x20id=\x22input-name\x22\x20onclick=\x22saveUserInfo();\x22>登録\x20▶</button>\x0a\x20\x20\x20\x20</p>',_0x1a2702=localStorage['getItem'](_0x5be771(0x90));if(_0x1a2702){const {name:_0x25ed68,code:_0x45341b,expireDate:_0x1dc330}=JSON[_0x5be771(0x86)](_0x1a2702),_0x596188=CryptoJS[_0x5be771(0x8e)]['decrypt'](_0x1dc330,''+_0x25ed68+_0x45341b)[_0x5be771(0x9b)](CryptoJS[_0x5be771(0xa8)][_0x5be771(0x88)]);dayjs()[_0x5be771(0xa4)](dayjs(_0x596188,DATE_FORMAT))?_0x22baa5[_0x5be771(0x92)]=_0x5be771(0xa0)+_0x25ed68+_0x5be771(0x83)+_0x45341b+_0x5be771(0x95):(localStorage[_0x5be771(0x9f)](_0x5be771(0x90)),_0x22baa5[_0x5be771(0x92)]=_0x174188);}else _0x22baa5[_0x5be771(0x92)]=_0x174188;};function makeUserCode(){const _0x2fe27f=_0x2b7280,_0x450141=_0x37597e=>_0x37597e[Math[_0x2fe27f(0x9e)](Math[_0x2fe27f(0x8b)]()*_0x37597e[_0x2fe27f(0xa6)])],_0x5634a4=['A','C','F','N','S'],_0x457755=['11','12','13','14','21','22','23','24','25','31','32','33','34'];return[_0x5634a4,_0x457755]['map'](_0x450141)[_0x2fe27f(0xa3)]('');}function _0x1e71(){const _0x3eccae=['code-area','parse','6696790xOGFqb','Utf8','setItem','encrypt','random','24yGDJfx','stringify','AES','YYYY-MM-DD\x20HH:mm:ss','yourInfo','646206SixHGh','innerHTML','5671215trCgsA','add','です。','982390rzYjLG','320225FkegIJ','getElementById','reload','startOf','toString','216279hvKrMi','192qoBoXB','floor','removeItem','<p>','your-name','format','join','isBefore','onload','length','93076TdKERw','enc','5uDutZi','value','さんの今日のユーザコードは','location'];_0x1e71=function(){return _0x3eccae;};return _0x1e71();}function saveUserInfo(){const _0x381357=_0x2b7280,_0x36d8a3=document[_0x381357(0x98)](_0x381357(0xa1))[_0x381357(0x82)],_0x4b2638=makeUserCode(),_0x306138=dayjs()[_0x381357(0x94)](0x1,'day')[_0x381357(0x9a)]('day')[_0x381357(0xa2)](DATE_FORMAT),_0x3df698=CryptoJS[_0x381357(0x8e)][_0x381357(0x8a)](_0x306138,''+_0x36d8a3+_0x4b2638)[_0x381357(0x9b)](),_0x2a17b0={'name':_0x36d8a3,'code':_0x4b2638,'expireDate':_0x3df698};localStorage[_0x381357(0x89)](_0x381357(0x90),JSON[_0x381357(0x8d)](_0x2a17b0)),document[_0x381357(0x84)][_0x381357(0x99)]();}
334
-
335
- ```
333
+ 'use strict';function _0x1d81(_0x6942b4,_0x34924a){const _0x1e71e1=_0x1e71();return _0x1d81=function(_0x1d8116,_0x127fa8){_0x1d8116=_0x1d8116-0x82;let _0x1f92bb=_0x1e71e1 ・・・以下省略
334
+
335
+ ```
336
+
337
+ (※回答文字数の上限を超えるため、省略しました)
336
338
 
337
339
 
338
340
 
339
341
  これでも万全ではありませんから、より安全にするには入力された名前からのユーザーコードの生成とそのコードの有効期限の管理を、サーバーサイドで行い、画面側でそのコードを使うときはサーバーサイドへそのコードがまだ使えるかの問い合わせをすることが考えられます。
342
+
343
+
344
+
345
+
346
+
347
+ ### 追記3
348
+
349
+
350
+
351
+ コメントから頂きました
352
+
353
+
354
+
355
+ > 今回のコードで名前の入力を2度目以降省略したい時には、
356
+
357
+
358
+
359
+ との件について回答します。ご提示の
360
+
361
+
362
+
363
+ > ”name”を単独でlocalStorageに保存した後、”if (yourInfoJson)”の外側に追加でif文を記載すれば宜しいのでしょうか。
364
+
365
+
366
+
367
+ という修正でも出来るとは思いますが、localStorageに保存する情報はこれまでに回答した`yourInfo`キーで保存する以下の形式のJSONオブジェクト
368
+
369
+
370
+
371
+ ```json
372
+
373
+ {"name":"山田太郎","code":"N23","expireDate":"U2FsdGVkX1+Nm9QtGzUMtF8UaJXYYtIQXAT4ODxj6o8p5o9skBrOqnGoQ0vji5Hw"}
374
+
375
+ ```
376
+
377
+
378
+
379
+ のみでも対応は可能かと思います。たとえば、**追記2**に記載したexpireDateを暗号化する修正後のコードに対して、さらに以下の**(1)**〜**(3)**のような修正を行います。
380
+
381
+
382
+
383
+ ##### (1) const inputHtml を固定のHTMLから、HTMLを返す関数にする。
384
+
385
+
386
+
387
+ `inputHtml`を修正して、保存されている名前を引数`name`で受け取り、それが
388
+
389
+ - undefined(などfalsy)のときには名前を入力できるtype="text"のinputとし
390
+
391
+ - 一文字以上の文字列のときは、type="hidden" のinputとする
392
+
393
+
394
+
395
+ ようなHTMLを返す関数にします。これは以下のような修正です。
396
+
397
+
398
+
399
+ ```diff
400
+
401
+ window.onload = function () {
402
+
403
+ const codeArea = document.getElementById('code-area');
404
+
405
+ - const inputHtml = `<p>名前を入力して今日のユーザコードを取得してください。</p>
406
+
407
+ + const inputHtml = name => `<p>${name ? '' : '名前を入力して'}今日のユーザコードを取得してください。</p>
408
+
409
+ <p>
410
+
411
+ - <input type="text" id="your-name" size="30" value="山田太郎">
412
+
413
+ + <input type="${name ? 'hidden' : 'text'}" id="your-name" size="30" value="${name || '山田太郎'}">
414
+
415
+ <button type="button" id="input-name" onclick="saveUserInfo();">登録 ▶</button>
416
+
417
+ </p>`;
418
+
419
+ ```
420
+
421
+
422
+
423
+ ##### (2) 上記の(1)で関数にした inputHtml を使う箇所の修正
424
+
425
+
426
+
427
+ 以下のように修正します。
428
+
429
+
430
+
431
+
432
+
433
+ - キー`yourInfo` で localStorage にJSONが保存されていた場合:
434
+
435
+
436
+
437
+ 現在日時が(暗号から戻された)expireDate以降となっている、または、localStorageに暗号化されたexpireDateが改ざんされるなどして、decryptedExpireDateが日付として不正となっている場合に、
438
+
439
+   ⅰ) localStorage から `yourInfo` をremoveItem()で消していたが、消さないでおく
440
+
441
+   ⅱ) codeArea.innerHTML に `inputHtml(name)` が返す、(inputのtypeがhiddenになっている)HTMLを入れる。
442
+
443
+
444
+
445
+ - キー`yourInfo` で localStorage にJSONが保存されていなかった場合:
446
+
447
+
448
+
449
+   ⅲ) codeArea.innerHTML に `inputHtml()` が返す、(inputのtypeがtextになっている)HTMLを入れる。
450
+
451
+
452
+
453
+ ```diff
454
+
455
+ if (dayjs().isBefore(dayjs(decryptedExpireDate, DATE_FORMAT))){
456
+
457
+ codeArea.innerHTML = `<p>${name}さんの今日のユーザコードは${code}です。`;
458
+
459
+ } else {
460
+
461
+ - localStorage.removeItem('yourInfo');
462
+
463
+ - codeArea.innerHTML = inputHtml;
464
+
465
+ + codeArea.innerHTML = inputHtml(name);
466
+
467
+ }
468
+
469
+ } else {
470
+
471
+ - codeArea.innerHTML = inputHtml;
472
+
473
+ + codeArea.innerHTML = inputHtml();
474
+
475
+ }
476
+
477
+ ```
478
+
479
+
480
+
481
+ ##### (3) 暗号化されたexpireDateの復号時に例外が発生した場合の対応
482
+
483
+
484
+
485
+ さらに、
486
+
487
+ > 今回のコードで名前の入力を2度目以降省略したい時には、
488
+
489
+
490
+
491
+ の件とは関係ありませんが、localStorageに保存されている暗号化されたexpireDateを、ユーザーが改ざんした場合に、
492
+
493
+ ```javascript
494
+
495
+ const decryptedExpireDate = CryptoJS.AES.decrypt(expireDate, `${name}${code}`).toString(CryptoJS.enc.Utf8);
496
+
497
+ ```
498
+
499
+ の部分で例外が発生する場合があるので、これを catch しておきます。
500
+
501
+
502
+
503
+ ```diff
504
+
505
+ if (yourInfoJson) {
506
+
507
+ const { name, code, expireDate } = JSON.parse(yourInfoJson);
508
+
509
+ - const decryptedExpireDate = CryptoJS.AES.decrypt(expireDate, `${name}${code}`).toString(CryptoJS.enc.Utf8);
510
+
511
+ + let decryptedExpireDate = null;
512
+
513
+ + try {
514
+
515
+ + decryptedExpireDate = CryptoJS.AES.decrypt(expireDate, `${name}${code}`).toString(CryptoJS.enc.Utf8);
516
+
517
+ + } catch (e) {
518
+
519
+ + console.error(e);
520
+
521
+ + }
522
+
523
+ if (dayjs().isBefore(dayjs(decryptedExpireDate, DATE_FORMAT))){
524
+
525
+ codeArea.innerHTML = `<p>${name}さんの今日のユーザコードは${code}です。`;
526
+
527
+
528
+
529
+ ```
530
+
531
+ この修正で、例外が発生した場合、`decryptedExpireDate`は`null` になりますが、その場合も
532
+
533
+ ```javascript
534
+
535
+ dayjs(decryptedExpireDate, DATE_FORMAT)
536
+
537
+ ```
538
+
539
+ はエラーにならず、日時として不正な内容を含むdayjsオブジェクトになり、それを比較対象にした isBefore 判定は(期待どおり) false を返してくれます。
540
+
541
+
542
+
543
+
544
+
545
+ 以上の修正**(1)**〜**(3)**によって、意図した動作のものになるかと思います。

3

テキスト追加

2021/12/07 15:53

投稿

退会済みユーザー
test CHANGED
@@ -250,7 +250,7 @@
250
250
 
251
251
 
252
252
 
253
- 上記までのコードだと、localStorageに保存されたyourInfoのexpireDateを改ざんすることで消滅期限を故意に伸ばすことができてしまうので暗号化します。暗号化には、[crypto-js](https://github.com/brix/crypto-js) を使います。
253
+ 上記までのコードだと、localStorageに保存されたyourInfoのexpireDateを改ざんすることで、有効期限を故意に伸ばすことができてしまうので暗号化します。暗号化には、[crypto-js](https://github.com/brix/crypto-js) を使います。
254
254
 
255
255
 
256
256
 

2

テキスト追加

2021/12/04 11:35

投稿

退会済みユーザー
test CHANGED
@@ -241,3 +241,99 @@
241
241
  ```
242
242
 
243
243
  によって現在日時と比較される日時として、より妥当なものになります。
244
+
245
+
246
+
247
+
248
+
249
+ ### 追記2
250
+
251
+
252
+
253
+ 上記までのコードだと、localStorageに保存されたyourInfoのexpireDateを改ざんすることで消滅期限を故意に伸ばすことができてしまうので暗号化します。。暗号化には、[crypto-js](https://github.com/brix/crypto-js) を使います。
254
+
255
+
256
+
257
+ ##### index.html
258
+
259
+ ```diff
260
+
261
+ crossorigin="anonymous"
262
+
263
+ referrerpolicy="no-referrer"></script>
264
+
265
+ + <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
266
+
267
+ <script src="script.js"></script>
268
+
269
+ </head>
270
+
271
+ <body>
272
+
273
+ ```
274
+
275
+
276
+
277
+ ##### script.js
278
+
279
+ ```diff
280
+
281
+ if (yourInfoJson) {
282
+
283
+ const { name, code, expireDate } = JSON.parse(yourInfoJson);
284
+
285
+ - if (dayjs().isBefore(dayjs(expireDate, DATE_FORMAT))){
286
+
287
+ + const decryptedExpireDate = CryptoJS.AES.decrypt(expireDate, `${name}${code}`).toString(CryptoJS.enc.Utf8);
288
+
289
+ + if (dayjs().isBefore(dayjs(decryptedExpireDate, DATE_FORMAT))){
290
+
291
+ codeArea.innerHTML = `<p>${name}さんの今日のユーザコードは${code}です。`;
292
+
293
+ } else {
294
+
295
+ localStorage.removeItem('yourInfo');
296
+
297
+ ```
298
+
299
+
300
+
301
+ ```diff
302
+
303
+ const name = document.getElementById('your-name').value;
304
+
305
+ const code = makeUserCode();
306
+
307
+ const expireDate = dayjs().add(1, 'day').startOf('day').format(DATE_FORMAT);
308
+
309
+ - const userInfo = { name, code, expireDate };
310
+
311
+ + const encryptedExpireDate = CryptoJS.AES.encrypt(expireDate,`${name}${code}`).toString();
312
+
313
+ + const userInfo = { name, code, expireDate: encryptedExpireDate };
314
+
315
+ localStorage.setItem('yourInfo', JSON.stringify(userInfo));
316
+
317
+ document.location.reload();
318
+
319
+ }
320
+
321
+ ```
322
+
323
+
324
+
325
+ しかし、これでも script.js が簡単に読めてしまっては、プログラムが読める人なら、expireDateの暗号化に、crypto-jsを使っていて、暗号化と復号化のカギに `${name}${code}` を使っていることが読み取れてしまいますので、 復号されたexpireDateが `2050-01-01 00:00:00` になるように、localStorageを書き換えてしまうかもしれません。これを防ぐために script.jsを難読化します。
326
+
327
+ たとえばJavaScript Obfuscator Tool というオンラインの難読化ツールを使うと、script.js が以下のように難読化されます。
328
+
329
+
330
+
331
+ ```javascript
332
+
333
+ 'use strict';function _0x1d81(_0x6942b4,_0x34924a){const _0x1e71e1=_0x1e71();return _0x1d81=function(_0x1d8116,_0x127fa8){_0x1d8116=_0x1d8116-0x82;let _0x1f92bb=_0x1e71e1[_0x1d8116];return _0x1f92bb;},_0x1d81(_0x6942b4,_0x34924a);}const _0x2b7280=_0x1d81;(function(_0x4c1d23,_0x555af9){const _0x486365=_0x1d81,_0x1f02da=_0x4c1d23();while(!![]){try{const _0x57dff9=parseInt(_0x486365(0x97))/0x1+parseInt(_0x486365(0x96))/0x2+-parseInt(_0x486365(0x8c))/0x3*(-parseInt(_0x486365(0xa7))/0x4)+parseInt(_0x486365(0xa9))/0x5*(parseInt(_0x486365(0x91))/0x6)+-parseInt(_0x486365(0x9c))/0x7*(parseInt(_0x486365(0x9d))/0x8)+-parseInt(_0x486365(0x93))/0x9+parseInt(_0x486365(0x87))/0xa;if(_0x57dff9===_0x555af9)break;else _0x1f02da['push'](_0x1f02da['shift']());}catch(_0x1e20ea){_0x1f02da['push'](_0x1f02da['shift']());}}}(_0x1e71,0x62759));const DATE_FORMAT=_0x2b7280(0x8f);window[_0x2b7280(0xa5)]=function(){const _0x5be771=_0x2b7280,_0x22baa5=document[_0x5be771(0x98)](_0x5be771(0x85)),_0x174188='<p>名前を入力して今日のユーザコードを取得してください。</p>\x0a\x20\x20\x20\x20<p>\x0a\x20\x20\x20\x20\x20\x20<input\x20type=\x22text\x22\x20id=\x22your-name\x22\x20size=\x2230\x22\x20value=\x22山田太郎\x22>\x0a\x20\x20\x20\x20\x20\x20<button\x20type=\x22button\x22\x20id=\x22input-name\x22\x20onclick=\x22saveUserInfo();\x22>登録\x20▶</button>\x0a\x20\x20\x20\x20</p>',_0x1a2702=localStorage['getItem'](_0x5be771(0x90));if(_0x1a2702){const {name:_0x25ed68,code:_0x45341b,expireDate:_0x1dc330}=JSON[_0x5be771(0x86)](_0x1a2702),_0x596188=CryptoJS[_0x5be771(0x8e)]['decrypt'](_0x1dc330,''+_0x25ed68+_0x45341b)[_0x5be771(0x9b)](CryptoJS[_0x5be771(0xa8)][_0x5be771(0x88)]);dayjs()[_0x5be771(0xa4)](dayjs(_0x596188,DATE_FORMAT))?_0x22baa5[_0x5be771(0x92)]=_0x5be771(0xa0)+_0x25ed68+_0x5be771(0x83)+_0x45341b+_0x5be771(0x95):(localStorage[_0x5be771(0x9f)](_0x5be771(0x90)),_0x22baa5[_0x5be771(0x92)]=_0x174188);}else _0x22baa5[_0x5be771(0x92)]=_0x174188;};function makeUserCode(){const _0x2fe27f=_0x2b7280,_0x450141=_0x37597e=>_0x37597e[Math[_0x2fe27f(0x9e)](Math[_0x2fe27f(0x8b)]()*_0x37597e[_0x2fe27f(0xa6)])],_0x5634a4=['A','C','F','N','S'],_0x457755=['11','12','13','14','21','22','23','24','25','31','32','33','34'];return[_0x5634a4,_0x457755]['map'](_0x450141)[_0x2fe27f(0xa3)]('');}function _0x1e71(){const _0x3eccae=['code-area','parse','6696790xOGFqb','Utf8','setItem','encrypt','random','24yGDJfx','stringify','AES','YYYY-MM-DD\x20HH:mm:ss','yourInfo','646206SixHGh','innerHTML','5671215trCgsA','add','です。','982390rzYjLG','320225FkegIJ','getElementById','reload','startOf','toString','216279hvKrMi','192qoBoXB','floor','removeItem','<p>','your-name','format','join','isBefore','onload','length','93076TdKERw','enc','5uDutZi','value','さんの今日のユーザコードは','location'];_0x1e71=function(){return _0x3eccae;};return _0x1e71();}function saveUserInfo(){const _0x381357=_0x2b7280,_0x36d8a3=document[_0x381357(0x98)](_0x381357(0xa1))[_0x381357(0x82)],_0x4b2638=makeUserCode(),_0x306138=dayjs()[_0x381357(0x94)](0x1,'day')[_0x381357(0x9a)]('day')[_0x381357(0xa2)](DATE_FORMAT),_0x3df698=CryptoJS[_0x381357(0x8e)][_0x381357(0x8a)](_0x306138,''+_0x36d8a3+_0x4b2638)[_0x381357(0x9b)](),_0x2a17b0={'name':_0x36d8a3,'code':_0x4b2638,'expireDate':_0x3df698};localStorage[_0x381357(0x89)](_0x381357(0x90),JSON[_0x381357(0x8d)](_0x2a17b0)),document[_0x381357(0x84)][_0x381357(0x99)]();}
334
+
335
+ ```
336
+
337
+
338
+
339
+ これでも万全ではありませんから、より安全にするには入力された名前からのユーザーコードの生成とそのコードの有効期限の管理を、サーバーサイドで行い、画面側でそのコードを使うときはサーバーサイドへそのコードがまだ使えるかの問い合わせをすることが考えられます。

1

テキスト追加

2021/12/04 11:33

投稿

退会済みユーザー
test CHANGED
@@ -207,3 +207,37 @@
207
207
 
208
208
 
209
209
  の2点を使っています。
210
+
211
+
212
+
213
+
214
+
215
+ ### 追記
216
+
217
+
218
+
219
+ 上記のコードで、`expireDate`を作るのに
220
+
221
+ ```javascript
222
+
223
+ const expireDate = dayjs().endOf('day').format(DATE_FORMAT);
224
+
225
+ ```
226
+
227
+ としていましたが、見直してみると、以下にするほうがより正確でした。
228
+
229
+ ```javascript
230
+
231
+ const expireDate = dayjs().add(1, 'day').startOf('day').format(DATE_FORMAT);
232
+
233
+ ```
234
+
235
+ これで、expireDate にはユーザーコードが生成された時点の翌日0時0分0秒に設定されるので、
236
+
237
+ ```javascript
238
+
239
+ if (dayjs().isBefore(dayjs(expireDate, DATE_FORMAT))){
240
+
241
+ ```
242
+
243
+ によって現在日時と比較される日時として、より妥当なものになります。