auth.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. #!/usr/bin/env python3
  2. """
  3. Usage::
  4. usage: auth.py [-h] [{google,apple,github,jwt}] [jwt]
  5. Login to your comma account
  6. positional arguments:
  7. {google,apple,github,jwt}
  8. jwt
  9. optional arguments:
  10. -h, --help show this help message and exit
  11. Examples::
  12. ./auth.py # Log in with google account
  13. ./auth.py github # Log in with GitHub Account
  14. ./auth.py jwt ey......hw # Log in with a JWT from https://jwt.comma.ai, for use in CI
  15. """
  16. import argparse
  17. import sys
  18. import pprint
  19. import webbrowser
  20. from http.server import BaseHTTPRequestHandler, HTTPServer
  21. from typing import Any
  22. from urllib.parse import parse_qs, urlencode
  23. from openpilot.tools.lib.api import APIError, CommaApi, UnauthorizedError
  24. from openpilot.tools.lib.auth_config import set_token, get_token
  25. PORT = 3000
  26. class ClientRedirectServer(HTTPServer):
  27. query_params: dict[str, Any] = {}
  28. class ClientRedirectHandler(BaseHTTPRequestHandler):
  29. def do_GET(self):
  30. if not self.path.startswith('/auth'):
  31. self.send_response(204)
  32. return
  33. query = self.path.split('?', 1)[-1]
  34. query_parsed = parse_qs(query, keep_blank_values=True)
  35. self.server.query_params = query_parsed
  36. self.send_response(200)
  37. self.send_header('Content-type', 'text/plain')
  38. self.end_headers()
  39. self.wfile.write(b'Return to the CLI to continue')
  40. def log_message(self, *args):
  41. pass # this prevent http server from dumping messages to stdout
  42. def auth_redirect_link(method):
  43. provider_id = {
  44. 'google': 'g',
  45. 'apple': 'a',
  46. 'github': 'h',
  47. }[method]
  48. params = {
  49. 'redirect_uri': f"https://api.comma.ai/v2/auth/{provider_id}/redirect/",
  50. 'state': f'service,localhost:{PORT}',
  51. }
  52. if method == 'google':
  53. params.update({
  54. 'type': 'web_server',
  55. 'client_id': '45471411055-ornt4svd2miog6dnopve7qtmh5mnu6id.apps.googleusercontent.com',
  56. 'response_type': 'code',
  57. 'scope': 'https://www.googleapis.com/auth/userinfo.email',
  58. 'prompt': 'select_account',
  59. })
  60. return 'https://accounts.google.com/o/oauth2/auth?' + urlencode(params)
  61. elif method == 'github':
  62. params.update({
  63. 'client_id': '28c4ecb54bb7272cb5a4',
  64. 'scope': 'read:user',
  65. })
  66. return 'https://github.com/login/oauth/authorize?' + urlencode(params)
  67. elif method == 'apple':
  68. params.update({
  69. 'client_id': 'ai.comma.login',
  70. 'response_type': 'code',
  71. 'response_mode': 'form_post',
  72. 'scope': 'name email',
  73. })
  74. return 'https://appleid.apple.com/auth/authorize?' + urlencode(params)
  75. else:
  76. raise NotImplementedError(f"no redirect implemented for method {method}")
  77. def login(method):
  78. oauth_uri = auth_redirect_link(method)
  79. web_server = ClientRedirectServer(('localhost', PORT), ClientRedirectHandler)
  80. print(f'To sign in, use your browser and navigate to {oauth_uri}')
  81. webbrowser.open(oauth_uri, new=2)
  82. while True:
  83. web_server.handle_request()
  84. if 'code' in web_server.query_params:
  85. break
  86. elif 'error' in web_server.query_params:
  87. print('Authentication Error: "{}". Description: "{}" '.format(
  88. web_server.query_params['error'],
  89. web_server.query_params.get('error_description')), file=sys.stderr)
  90. break
  91. try:
  92. auth_resp = CommaApi().post('v2/auth/', data={'code': web_server.query_params['code'], 'provider': web_server.query_params['provider']})
  93. set_token(auth_resp['access_token'])
  94. except APIError as e:
  95. print(f'Authentication Error: {e}', file=sys.stderr)
  96. if __name__ == '__main__':
  97. parser = argparse.ArgumentParser(description='Login to your comma account')
  98. parser.add_argument('method', default='google', const='google', nargs='?', choices=['google', 'apple', 'github', 'jwt'])
  99. parser.add_argument('jwt', nargs='?')
  100. args = parser.parse_args()
  101. if args.method == 'jwt':
  102. if args.jwt is None:
  103. print("method JWT selected, but no JWT was provided")
  104. exit(1)
  105. set_token(args.jwt)
  106. else:
  107. login(args.method)
  108. try:
  109. me = CommaApi(token=get_token()).get('/v1/me')
  110. print("Authenticated!")
  111. pprint.pprint(me)
  112. except UnauthorizedError:
  113. print("Got invalid JWT")
  114. exit(1)