forro

End-to-end encrypted contract form based on PGP.
git clone git://git.defalsify.org/forro.git
Info | Log | Files | Refs | README | LICENSE

index.html (10737B)


      1 <html>
      2 	<head>
      3 		<title>Forro contact form</title>
      4 		<meta charset="UTF-8" />
      5 		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
      6 		<script defer src="node_modules/alpinejs/dist/cdn.min.js"></script>
      7 		<script src="node_modules/openpgp/dist/openpgp.min.js"></script>
      8 		<script src="node_modules/jssha/dist/sha256.js"></script>
      9 		<script src="node_modules/MimeJS/src/base64.js"></script>
     10 		<script src="node_modules/MimeJS/dist/mime-js.min.js"></script>
     11 		<script src="top.js"></script>
     12 		<script src="key.js"></script>
     13 		<script src="name.js"></script>
     14 		<script src="help.js"></script>
     15 		<script src="app.js"></script>
     16 		<link rel="stylesheet" type="text/css" href="style.css"></link>
     17 	</head>
     18 
     19 	<body 
     20 	
     21 	x-data="{
     22 		unlock_set: false,
     23 		message_status: '',
     24 		message_count: g_counter,
     25 		last_state: 0,
     26 
     27 		get haveSettings() {
     28 			return this.last_state & STATE['SETTINGS'] > 0;
     29 		},
     30 
     31 		get isDev() {
     32 			return checkState(this.last_state, STATE.DEV);
     33 		},
     34 
     35 		get isHelp() {
     36 			return checkState(this.last_state, STATE.HELP);
     37 		},
     38 
     39 		get lastState() {
     40 			return debugState(this.last_state);
     41 		},
     42 
     43 		get haveKey() {
     44 			return checkState(this.last_state, STATE['LOCAL_KEY']);
     45 		},
     46 
     47 		get unlockedKey() {
     48 			return checkState(this.last_state, STATE['LOCAL_KEY_DECRYPTED']);
     49 		},
     50 
     51 		get haveRemoteKey() {
     52 			return checkState(this.last_state, STATE['REMOTE_KEY']);
     53 		},
     54 
     55 		get getTitle() {
     56 			return 'forro v' + g_version;
     57 		},
     58 
     59 		}"
     60 
     61 		      x-init="unlockLocalKey();"
     62 
     63 		      @messagestatechange.window='
     64 				message_status = $event.detail.s;
     65 				message_count = $event.detail.c;
     66 				last_state = $event.detail.state;
     67 				if (!unlock_set && checkState(STATE["LOCAL_KEY_DECRYPTED"])) {
     68 		      			unlock_set = true;
     69 					$dispatch("unlocked");
     70 				}
     71 				if (checkState(STATE["PASSPHRASE_FAIL"])) {
     72 					$dispatch("passfail");
     73 				}
     74 				if (checkState(STATE["ACK_MESSAGE"])) {
     75 				      $dispatch("rcpt", {v: $event.detail.s[1]});
     76 				}
     77 				'
     78 
     79 		      @unlocked='applyLocalKey();'
     80 
     81 		      @rst.window='unlock_set = false; message_count = 0'
     82 
     83 		      >
     84 
     85 			
     86 		      
     87 		<div id="app"> 
     88 			
     89 
     90 			<h1><a href="https://defalsify.org/git/forro"
     91 				x-text="getTitle"></a>
     92 			 </h1>
     93 
     94 			
     95 		<div id="localkey" x-data="{
     96 			passphrase_cache: '',
     97 			passphrase_status: '',
     98 			passphrase_default_status: 'create key with passphrase',
     99 			get passphraseStatus() {
    100 				return this.passphrase_status ? this.passphrase_status : this.passphrase_default_status;
    101 			},
    102 		     }"
    103 		     
    104 		      
    105 
    106 			x-show='!unlockedKey'
    107 			class="localkey-container"
    108 
    109 			>
    110 			<input name="pwd" type="password"
    111 				x-model='passphrase_cache'
    112 				x-bind:placeholder='passphraseStatus'
    113 				@passfail.window='passphrase_status = "wrong_passphrase"; passphrase_cache = "";';
    114 				@messagestatechange.window='if (checkState(STATE["LOCAL_KEY"])) { passphrase_default_status = "please unlock key"; };'
    115 				@rst.window='passphrase_status = "please create new key";' 
    116 				class="input-passphrase"
    117 
    118 					  >
    119 			<button x-data="{
    120 					go_label: 'go',
    121 				}"
    122 
    123 				x-text='go_label'
    124 
    125 				@passfail.window='go_label = "go again";'
    126 				@rst.window='go_label = "go";'
    127 
    128 				@click='setPwd(passphrase_cache);'
    129 				class="btn-primary"
    130 
    131 				></button>
    132 				
    133 
    134 			<button x-show='!haveKey' @click='tryHelpFor("nopass"); setPwd();'              class="btn-secondary"
    135 			>without passphrase</button>
    136 		</div>
    137 
    138 
    139 		<div class="container">
    140 
    141 		<div class="form" id="message_panel"
    142 			x-show='unlockedKey'
    143 			x-init='setUp();'
    144 			x-data="{
    145 				rcpt: '',
    146 				content: '',
    147 				key: '',
    148 				rkey: '',
    149 				key_name: '',
    150 				rkey_name: '',
    151 				defaultname: true,
    152 				key_content: '',
    153 				rkey_content: '',
    154 				filez: {},
    155 
    156 				get localKeyArmor() {
    157 					return 'data:text/plain;charset=utf8,' + this.key_content;
    158 				},
    159 				get localKeyFilename() {
    160 					return 'privatekey_' + this.key + '.asc';
    161 				},
    162 				get remoteKeyArmor() {
    163 					return 'data:text/plain;charset=utf8,' + this.rkey_content;
    164 				},
    165 				get remoteKeyFilename() {
    166 					return 'publickey_' + this.rkey + '.asc';
    167 				},
    168 				get keyDisplay() {
    169 					if (!this.key) {
    170 						return '';
    171 					}
    172 					return this.key + ' (' + this.key_name + ')';
    173 				},
    174 				get rkeyDisplay() {
    175 					if (!this.rkey) {
    176 						return '';
    177 					}
    178 					return this.rkey + ' (' + this.rkey_name + ')';
    179 				},
    180 
    181 				addFileToList(k, v) {
    182 					this.filez[k] = v;
    183 					document.getElementById('fileAdder').value='';
    184 				},
    185 
    186 				purgeFiles() {
    187 					this.filez = {};
    188 				},
    189 
    190 				get fileList() {
    191 					let files = [];
    192 					for (const k in this.filez) {
    193 						files.push(this.filez[k] + ' (' + k.substring(0, 8) + ')');
    194 					}
    195 					return files;
    196 				},
    197 				}"
    198 			@rcpt.window='rcpt = $event.detail.v;'
    199 			@messagestatechange.window='
    200 				key = $event.detail.kl;
    201 				rkey = $event.detail.kr; 
    202 				key_name = $event.detail.nl;
    203 				rkey_name = $event.detail.nr;
    204 
    205 				if (checkState(STATE["LOCAL_KEY"])) {
    206 					if (key_content == "" | key_content == null) {
    207 						key_content = g_local_key.armor();
    208 					}
    209 				}
    210 				if (checkState(STATE["REMOTE_KEY"])) {
    211 					if (rkey_content == "") {
    212 						rkey_content = g_remote_key.armor();
    213 					}
    214 				}
    215 				if (checkState(STATE.ACK_MESSAGE)) {
    216 					purgeFiles();
    217 					document.getElementById("fileAdder").value="";
    218 				}
    219 		       '>
    220 			   
    221 			
    222 				<div id="helpdiv" 
    223 					  x-show="isHelp"
    224 					  >
    225 					  <h3 class="title">Let's get in touch</h3>
    226 
    227 				<template x-data="{
    228 					help_cap: 128, // if set to 0 then no buffer
    229 					help_display_count: 5, // if help_cap is 0, ignore
    230 					help_contents: [],
    231 					help_lastcount: 0,
    232 					help_count: 0,
    233 					help_crsr: -1,
    234 
    235 					addHelpContents(v) {
    236 						if (this.help_cap == 0) {
    237 							this.help_display_count = v.length;
    238 							this.help_count = v.length;
    239 							this.help_contents = v;
    240 							this.help_crsr = this.help_count - 1;
    241 						} else {
    242 							for (let i = 0; i < v.length; i++) {
    243 								this.help_crsr += 1;
    244 								this.help_crsr %= this.help_cap;
    245 								this.help_contents[this.help_crsr] = v[i];
    246 								if (this.help_count < this.help_cap) {
    247 									this.help_count += 1;
    248 								}
    249 							}
    250 						}
    251 						if (v.length > 0) {
    252 							this.help_lastcount = v.length;
    253 						}
    254 					},
    255 
    256 					get helpContents() {
    257 						if (this.help_crsr < 0) {
    258 							return [];
    259 						}
    260 						let contents = [];
    261 
    262 						let l = this.help_display_count;
    263 						if (l > this.help_count) {
    264 							l = this.help_count;
    265 						}
    266 
    267 
    268 						let c = (this.help_crsr - l + 1);
    269 						if (c < 0) {
    270 							c = l + c;
    271 						}
    272 						
    273 						new_threshold = l - this.help_lastcount;
    274 						for (let i = 0; i < l; i++) {
    275 							if (this.help_cap > 0) {
    276 								c %= this.help_cap;
    277 							}
    278 							is_old = i < new_threshold;
    279 							v = [this.help_contents[c], is_old ? 'old' : 'new'];
    280 							contents.push(v); //this.help_contents[c]);
    281 							c += 1;
    282 						}
    283 						return contents;
    284 					},
    285 					}"
    286 					x-init="tryHelpFor('welcome');"
    287 					x-for="(v) in helpContents"
    288 					@help.window="addHelpContents($event.detail.v);">
    289 					<p x-html="v[0]" :class="v[1] == 'old' ? 'help-text old' : 'help-text new'" />
    290 
    291 				</template>
    292 			</div>
    293 
    294 				<div class="contact-form">
    295 				
    296 
    297 		 
    298 			<form autocomplete="off">
    299 				<h3 class="title">Get in touch!</h3>
    300                   <div class="details">
    301               
    302 				<div >
    303 						<label for="status">Status:</label>
    304 				        <span x-text="message_status" x-on:messagestatechange.window="defaultname = !g_local_key_identified;"></span>
    305 					</div>
    306 
    307 				<div>
    308                    <label for="your-identity">Your identity:</label>
    309 				   <p><a x-text="keyDisplay" title="Click to download your private key" x-bind:href="localKeyArmor" x-bind:download="localKeyFilename"></a></p>
    310 				</div>
    311 
    312 
    313 				
    314 				<div>
    315 					<label for="their-identity">Their identity:</label>
    316 				    <p><a x-text="rkeyDisplay" title="Click to download the recipient's public key" x-bind:href="remoteKeyArmor" x-bind:download="remoteKeyFilename"></a></p>
    317 				</div>
    318 				
    319 				
    320 				<div>
    321 					<label for="message-number">Message number:</label>
    322 				<span x-text="message_count"></span>
    323 				</div>
    324 
    325 				<div>
    326 					<label for="receipt">Your receipt:</label>
    327 				    <p><a x-bind:href="g_data_endpoint + '/' + rcpt" x-text="rcpt"></a></p>
    328 				</div>
    329 			</div>	
    330 		<div class="input-container textarea">
    331 			<label for="">Message:</label>
    332 
    333 			<textarea 
    334 			 name="message"
    335 			 placeholder="message"
    336 			 class="input"
    337 				x-model="content"
    338 				@focus="tryHelpFor('writemsg');"
    339 				@messagestatechange.window="if (checkState(STATE.ACK_MESSAGE)) {console.log('foo'); content = '';}"
    340 				>
    341 			</textarea>
    342 		</div>
    343 		<div class="input-container">
    344 			<input  class="btn" type="file" id="fileAdder"
    345 				@change="fileChange();"
    346 				   />
    347 			<ol>
    348 				<template x-data="{
    349 					  					  
    350 					  };"
    351 					x-for="(v) in fileList"
    352 					@messagestatechange.window="if (checkState(STATE.FILE_ADDED)) {addFileToList($event.detail.s[0], $event.detail.s[1]);}"
    353 					>
    354 					<li x-text="v"></li>
    355 				</template>
    356 			</ol>
    357 			</div>
    358 
    359 			<div x-data='{ 
    360 				realname: "",
    361 				realemail: "",
    362 					    }'>
    363 				<div x-show='defaultname  && !g_local_key_identified'
    364 				     x-data='{
    365 					identify: false,
    366 				     }'
    367 				     @rst.window='identify = false;'
    368 				     >
    369 				     <select id="identity_select" @change='identify = $event.target.value > 0; if (identify) { tryHelpFor("identify") };'
    370 						@rst.window='document.getElementById("identity_select").value=0;'>
    371 						<option value=0 defaultselected>Stay anonymous</option>
    372 						<option value=1>Identify yourself</option>
    373 					</select>
    374 					<div class="input-container identify" x-show="identify">
    375 						
    376 						<input name="id_name" class="input" type="text" placeholder="Name" x-model="realname" />
    377 						<input name="id_email"  class="input" type="email" placeholder="Email" x-model="realemail" />
    378   
    379  
    380 					</div>
    381 				</div>
    382 				<div x-data="{
    383 						ready: false,
    384 						}"
    385 					x-on:messagestatechange.window='ready = checkState(STATE["RTS"]);'
    386 						>
    387 					<button 
    388 						class="btn"
    389 						x-bind:disabled='!ready'
    390 						@click="tryDispatch(content, realname, realemail, filez);">
    391 						sign, encrypt and send
    392 					</button>
    393 				</div>
    394 			</div>
    395 			
    396 		</form>  
    397 		<div id="reset" x-data="{ rst: false }">
    398 			<button  x-show="haveKey && !rst" @click="rst = true;">Discard key</button>
    399 			<button  x-show="rst" @click="if (confirm('Are you sure you want to discard the key?')) { rst = false; $dispatch('rst'); purgeLocalKey(); }">Discard key</button> 
    400 		</div>
    401 			</div>
    402 		</div>
    403 	</div>
    404 		
    405 		
    406 		<div id="dev" x-show='isDev'>
    407 		<h2>Devmode details</h2>
    408 			<dl>
    409 				<dt>last event</dt>
    410 				<dd x-text='message_status' />
    411 				<dt>state value</dt>
    412 				<dd x-text='last_state' />
    413 				<dt>state description</dt>
    414 				<dd x-text='lastState' />
    415 			</dl>
    416 		</div>
    417 	</body>
    418 </html>