Browse code

add web CLI

Joseph Weston authored on 15/04/2018 18:31:54
Showing 1 changed files
... ...
@@ -22,12 +22,15 @@ import signal
22 22
 import logging
23 23
 import argparse
24 24
 import asyncio
25
+import ipaddress
25 26
 
26 27
 import structlog
27 28
 from termcolor import colored
28 29
 import aiohttp
30
+import aiohttp.web
29 31
 
30 32
 from . import api, vpn, __version__
33
+from . import web as nord_web
31 34
 from ._utils import sudo_requires_password, prompt_for_sudo, LockError
32 35
 
33 36
 
... ...
@@ -163,6 +166,29 @@ def parse_arguments():
163 166
                                 help='Reject hosts that have a load greater '
164 167
                                      'than this threshold')
165 168
 
169
+    web_parser = subparsers.add_parser(
170
+        'web',
171
+        help="Run nord as a web app",
172
+        description="Serve a web app that provides a GUI for selecting the "
173
+                    "country to connect to.")
174
+
175
+    web_parser.add_argument('--debug', action='store_true',
176
+                            help='Print debugging information')
177
+    web_parser.add_argument('-u', '--username', type=str,
178
+                            required=True,
179
+                            help='NordVPN account username')
180
+    web_parser.add_argument('-P', '--port', type=int, default=8000,
181
+                            help='Port on which to run the web app')
182
+    web_parser.add_argument('-H', '--host', type=ipaddress.ip_address,
183
+                            default='127.0.0.1',
184
+                            help='IP address on which to run the web app')
185
+    # methods of password entry
186
+    passwd = web_parser.add_mutually_exclusive_group(required=True)
187
+    passwd.add_argument('-p', '--password', type=str,
188
+                        help='NordVPN account password')
189
+    passwd.add_argument('-f', '--password-file', type=argparse.FileType(),
190
+                        help='Path to file containing NordVPN password')
191
+
166 192
     args = parser.parse_args()
167 193
 
168 194
     if not args.command:
... ...
@@ -218,6 +244,44 @@ async def connect(args):
218 244
         raise Abort(str(error))
219 245
 
220 246
 
247
+async def web(args):
248
+    """Run nord as a web app"""
249
+
250
+    username = args.username
251
+    password = args.password or args.password_file.readline().strip()
252
+
253
+    # Group requests together to reduce overall latency
254
+    async with api.Client() as client:
255
+        output = await asyncio.gather(
256
+            client.valid_credentials(username, password),
257
+            sudo_requires_password(),
258
+        )
259
+        valid_credentials, require_sudo = output
260
+
261
+        if not valid_credentials:
262
+            raise Abort('invalid username/password combination')
263
+
264
+        if require_sudo:
265
+            print('sudo password required for OpenVPN')
266
+            try:
267
+                await prompt_for_sudo()
268
+            except PermissionError:
269
+                # 'sudo' will already have notified the user about the failure
270
+                raise Abort()
271
+
272
+        app = nord_web.init_app(client, (username, password))
273
+        runner = aiohttp.web.AppRunner(app, handle_signals=True)
274
+        await runner.setup()
275
+        site = aiohttp.web.TCPSite(runner, str(args.host), args.port)
276
+        await site.start()
277
+        print(colored(f'=== Listening {args.host}:{args.port} ===',
278
+                      color='white', attrs=['bold']))
279
+        try:
280
+            await app['shutdown_signal'].wait()
281
+        finally:
282
+            await runner.cleanup()
283
+
284
+
221 285
 async def _get_host_and_config(client, args):
222 286
     # get the host
223 287
     if args.server: