teratail header banner
teratail header banner
質問するログイン新規登録

回答編集履歴

5

テキスト追加

2021/12/07 15:58

投稿

退会済みユーザー
answer CHANGED
@@ -191,7 +191,7 @@
191
191
 
192
192
  ##### (1) const inputHtml を固定のHTMLから、HTMLを返す関数にする。
193
193
 
194
- `inputHtml`を修正して、保存されている名前を引数`name`で受け取り、それが
194
+ `inputHtml`を修正して、名前を引数`name`で受け取り、それが
195
195
  - undefined(などfalsy)のときには名前を入力できるtype="text"のinputとし
196
196
  - 一文字以上の文字列のときは、type="hidden" のinputとする
197
197
 

4

テキスト追加

2021/12/07 15:58

投稿

退会済みユーザー
answer CHANGED
@@ -164,7 +164,110 @@
164
164
  たとえばJavaScript Obfuscator Tool というオンラインの難読化ツールを使うと、script.js が以下のように難読化されます。
165
165
 
166
166
  ```javascript
167
- '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)]();}
167
+ 'use strict';function _0x1d81(_0x6942b4,_0x34924a){const _0x1e71e1=_0x1e71();return _0x1d81=function(_0x1d8116,_0x127fa8){_0x1d8116=_0x1d8116-0x82;let _0x1f92bb=_0x1e71e1 ・・・以下省略
168
168
  ```
169
+ (※回答文字数の上限を超えるため、省略しました)
169
170
 
170
- これでも万全ではありませんから、より安全にするには入力された名前からのユーザーコードの生成とそのコードの有効期限の管理を、サーバーサイドで行い、画面側でそのコードを使うときはサーバーサイドへそのコードがまだ使えるかの問い合わせをすることが考えられます。
171
+ これでも万全ではありませんから、より安全にするには入力された名前からのユーザーコードの生成とそのコードの有効期限の管理を、サーバーサイドで行い、画面側でそのコードを使うときはサーバーサイドへそのコードがまだ使えるかの問い合わせをすることが考えられます。
172
+
173
+
174
+ ### 追記3
175
+
176
+ コメントから頂きました
177
+
178
+ > 今回のコードで名前の入力を2度目以降省略したい時には、
179
+
180
+ との件について回答します。ご提示の
181
+
182
+ > ”name”を単独でlocalStorageに保存した後、”if (yourInfoJson)”の外側に追加でif文を記載すれば宜しいのでしょうか。
183
+
184
+ という修正でも出来るとは思いますが、localStorageに保存する情報はこれまでに回答した`yourInfo`キーで保存する以下の形式のJSONオブジェクト
185
+
186
+ ```json
187
+ {"name":"山田太郎","code":"N23","expireDate":"U2FsdGVkX1+Nm9QtGzUMtF8UaJXYYtIQXAT4ODxj6o8p5o9skBrOqnGoQ0vji5Hw"}
188
+ ```
189
+
190
+ のみでも対応は可能かと思います。たとえば、**追記2**に記載したexpireDateを暗号化する修正後のコードに対して、さらに以下の**(1)**〜**(3)**のような修正を行います。
191
+
192
+ ##### (1) const inputHtml を固定のHTMLから、HTMLを返す関数にする。
193
+
194
+ `inputHtml`を修正して、保存されている名前を引数`name`で受け取り、それが
195
+ - undefined(などfalsy)のときには名前を入力できるtype="text"のinputとし
196
+ - 一文字以上の文字列のときは、type="hidden" のinputとする
197
+
198
+ ようなHTMLを返す関数にします。これは以下のような修正です。
199
+
200
+ ```diff
201
+ window.onload = function () {
202
+ const codeArea = document.getElementById('code-area');
203
+ - const inputHtml = `<p>名前を入力して今日のユーザコードを取得してください。</p>
204
+ + const inputHtml = name => `<p>${name ? '' : '名前を入力して'}今日のユーザコードを取得してください。</p>
205
+ <p>
206
+ - <input type="text" id="your-name" size="30" value="山田太郎">
207
+ + <input type="${name ? 'hidden' : 'text'}" id="your-name" size="30" value="${name || '山田太郎'}">
208
+ <button type="button" id="input-name" onclick="saveUserInfo();">登録 ▶</button>
209
+ </p>`;
210
+ ```
211
+
212
+ ##### (2) 上記の(1)で関数にした inputHtml を使う箇所の修正
213
+
214
+ 以下のように修正します。
215
+
216
+
217
+ - キー`yourInfo` で localStorage にJSONが保存されていた場合:
218
+
219
+ 現在日時が(暗号から戻された)expireDate以降となっている、または、localStorageに暗号化されたexpireDateが改ざんされるなどして、decryptedExpireDateが日付として不正となっている場合に、
220
+   ⅰ) localStorage から `yourInfo` をremoveItem()で消していたが、消さないでおく
221
+   ⅱ) codeArea.innerHTML に `inputHtml(name)` が返す、(inputのtypeがhiddenになっている)HTMLを入れる。
222
+
223
+ - キー`yourInfo` で localStorage にJSONが保存されていなかった場合:
224
+
225
+   ⅲ) codeArea.innerHTML に `inputHtml()` が返す、(inputのtypeがtextになっている)HTMLを入れる。
226
+
227
+ ```diff
228
+ if (dayjs().isBefore(dayjs(decryptedExpireDate, DATE_FORMAT))){
229
+ codeArea.innerHTML = `<p>${name}さんの今日のユーザコードは${code}です。`;
230
+ } else {
231
+ - localStorage.removeItem('yourInfo');
232
+ - codeArea.innerHTML = inputHtml;
233
+ + codeArea.innerHTML = inputHtml(name);
234
+ }
235
+ } else {
236
+ - codeArea.innerHTML = inputHtml;
237
+ + codeArea.innerHTML = inputHtml();
238
+ }
239
+ ```
240
+
241
+ ##### (3) 暗号化されたexpireDateの復号時に例外が発生した場合の対応
242
+
243
+ さらに、
244
+ > 今回のコードで名前の入力を2度目以降省略したい時には、
245
+
246
+ の件とは関係ありませんが、localStorageに保存されている暗号化されたexpireDateを、ユーザーが改ざんした場合に、
247
+ ```javascript
248
+ const decryptedExpireDate = CryptoJS.AES.decrypt(expireDate, `${name}${code}`).toString(CryptoJS.enc.Utf8);
249
+ ```
250
+ の部分で例外が発生する場合があるので、これを catch しておきます。
251
+
252
+ ```diff
253
+ if (yourInfoJson) {
254
+ const { name, code, expireDate } = JSON.parse(yourInfoJson);
255
+ - const decryptedExpireDate = CryptoJS.AES.decrypt(expireDate, `${name}${code}`).toString(CryptoJS.enc.Utf8);
256
+ + let decryptedExpireDate = null;
257
+ + try {
258
+ + decryptedExpireDate = CryptoJS.AES.decrypt(expireDate, `${name}${code}`).toString(CryptoJS.enc.Utf8);
259
+ + } catch (e) {
260
+ + console.error(e);
261
+ + }
262
+ if (dayjs().isBefore(dayjs(decryptedExpireDate, DATE_FORMAT))){
263
+ codeArea.innerHTML = `<p>${name}さんの今日のユーザコードは${code}です。`;
264
+
265
+ ```
266
+ この修正で、例外が発生した場合、`decryptedExpireDate`は`null` になりますが、その場合も
267
+ ```javascript
268
+ dayjs(decryptedExpireDate, DATE_FORMAT)
269
+ ```
270
+ はエラーにならず、日時として不正な内容を含むdayjsオブジェクトになり、それを比較対象にした isBefore 判定は(期待どおり) false を返してくれます。
271
+
272
+
273
+ 以上の修正**(1)**〜**(3)**によって、意図した動作のものになるかと思います。

3

テキスト追加

2021/12/07 15:53

投稿

退会済みユーザー
answer CHANGED
@@ -124,7 +124,7 @@
124
124
 
125
125
  ### 追記2
126
126
 
127
- 上記までのコードだと、localStorageに保存されたyourInfoのexpireDateを改ざんすることで消滅期限を故意に伸ばすことができてしまうので暗号化します。暗号化には、[crypto-js](https://github.com/brix/crypto-js) を使います。
127
+ 上記までのコードだと、localStorageに保存されたyourInfoのexpireDateを改ざんすることで、有効期限を故意に伸ばすことができてしまうので暗号化します。暗号化には、[crypto-js](https://github.com/brix/crypto-js) を使います。
128
128
 
129
129
  ##### index.html
130
130
  ```diff

2

テキスト追加

2021/12/04 11:35

投稿

退会済みユーザー
answer CHANGED
@@ -119,4 +119,52 @@
119
119
  ```javascript
120
120
  if (dayjs().isBefore(dayjs(expireDate, DATE_FORMAT))){
121
121
  ```
122
- によって現在日時と比較される日時として、より妥当なものになります。
122
+ によって現在日時と比較される日時として、より妥当なものになります。
123
+
124
+
125
+ ### 追記2
126
+
127
+ 上記までのコードだと、localStorageに保存されたyourInfoのexpireDateを改ざんすることで消滅期限を故意に伸ばすことができてしまうので暗号化します。。暗号化には、[crypto-js](https://github.com/brix/crypto-js) を使います。
128
+
129
+ ##### index.html
130
+ ```diff
131
+ crossorigin="anonymous"
132
+ referrerpolicy="no-referrer"></script>
133
+ + <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
134
+ <script src="script.js"></script>
135
+ </head>
136
+ <body>
137
+ ```
138
+
139
+ ##### script.js
140
+ ```diff
141
+ if (yourInfoJson) {
142
+ const { name, code, expireDate } = JSON.parse(yourInfoJson);
143
+ - if (dayjs().isBefore(dayjs(expireDate, DATE_FORMAT))){
144
+ + const decryptedExpireDate = CryptoJS.AES.decrypt(expireDate, `${name}${code}`).toString(CryptoJS.enc.Utf8);
145
+ + if (dayjs().isBefore(dayjs(decryptedExpireDate, DATE_FORMAT))){
146
+ codeArea.innerHTML = `<p>${name}さんの今日のユーザコードは${code}です。`;
147
+ } else {
148
+ localStorage.removeItem('yourInfo');
149
+ ```
150
+
151
+ ```diff
152
+ const name = document.getElementById('your-name').value;
153
+ const code = makeUserCode();
154
+ const expireDate = dayjs().add(1, 'day').startOf('day').format(DATE_FORMAT);
155
+ - const userInfo = { name, code, expireDate };
156
+ + const encryptedExpireDate = CryptoJS.AES.encrypt(expireDate,`${name}${code}`).toString();
157
+ + const userInfo = { name, code, expireDate: encryptedExpireDate };
158
+ localStorage.setItem('yourInfo', JSON.stringify(userInfo));
159
+ document.location.reload();
160
+ }
161
+ ```
162
+
163
+ しかし、これでも script.js が簡単に読めてしまっては、プログラムが読める人なら、expireDateの暗号化に、crypto-jsを使っていて、暗号化と復号化のカギに `${name}${code}` を使っていることが読み取れてしまいますので、 復号されたexpireDateが `2050-01-01 00:00:00` になるように、localStorageを書き換えてしまうかもしれません。これを防ぐために script.jsを難読化します。
164
+ たとえばJavaScript Obfuscator Tool というオンラインの難読化ツールを使うと、script.js が以下のように難読化されます。
165
+
166
+ ```javascript
167
+ '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)]();}
168
+ ```
169
+
170
+ これでも万全ではありませんから、より安全にするには入力された名前からのユーザーコードの生成とそのコードの有効期限の管理を、サーバーサイドで行い、画面側でそのコードを使うときはサーバーサイドへそのコードがまだ使えるかの問い合わせをすることが考えられます。

1

テキスト追加

2021/12/04 11:33

投稿

退会済みユーザー
answer CHANGED
@@ -102,4 +102,21 @@
102
102
  - 二つの日時の比較を行うための[isBefore](https://day.js.org/docs/en/query/is-before)
103
103
  - ある日の最終日時を作るために[endOf](https://day.js.org/docs/en/manipulate/end-of)
104
104
 
105
- の2点を使っています。
105
+ の2点を使っています。
106
+
107
+
108
+ ### 追記
109
+
110
+ 上記のコードで、`expireDate`を作るのに
111
+ ```javascript
112
+ const expireDate = dayjs().endOf('day').format(DATE_FORMAT);
113
+ ```
114
+ としていましたが、見直してみると、以下にするほうがより正確でした。
115
+ ```javascript
116
+ const expireDate = dayjs().add(1, 'day').startOf('day').format(DATE_FORMAT);
117
+ ```
118
+ これで、expireDate にはユーザーコードが生成された時点の翌日0時0分0秒に設定されるので、
119
+ ```javascript
120
+ if (dayjs().isBefore(dayjs(expireDate, DATE_FORMAT))){
121
+ ```
122
+ によって現在日時と比較される日時として、より妥当なものになります。