diff --git a/api/Pipfile b/api/Pipfile index 57e34d3..1d69d63 100644 --- a/api/Pipfile +++ b/api/Pipfile @@ -8,6 +8,7 @@ django = "*" psycopg2-binary = "*" google-cloud-storage = "*" django-cors-headers = "*" +django-allauth = {extras = ["socialaccount"], version = "*"} [dev-packages] diff --git a/api/Pipfile.lock b/api/Pipfile.lock index 93e044e..0859d08 100644 --- a/api/Pipfile.lock +++ b/api/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "2a2c8096be188d70ebf219f6ae7e8760cc504d98993c815c866e074f1602350b" + "sha256": "5091a015380693ee93b053a09b500a7e4f06a4ebfaf50f199f1c2835306291d3" }, "pipfile-spec": 6, "requires": { @@ -40,6 +40,64 @@ "markers": "python_version >= '3.6'", "version": "==2024.7.4" }, + "cffi": { + "hashes": [ + "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc", + "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a", + "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417", + "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab", + "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520", + "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36", + "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743", + "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8", + "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed", + "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684", + "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56", + "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324", + "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d", + "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235", + "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e", + "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088", + "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000", + "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7", + "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e", + "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673", + "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c", + "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe", + "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2", + "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098", + "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8", + "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a", + "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0", + "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b", + "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896", + "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e", + "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9", + "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2", + "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b", + "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6", + "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404", + "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f", + "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0", + "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4", + "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc", + "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936", + "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba", + "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872", + "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb", + "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614", + "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1", + "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d", + "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969", + "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b", + "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4", + "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627", + "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956", + "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357" + ], + "markers": "platform_python_implementation != 'PyPy'", + "version": "==1.16.0" + }, "charset-normalizer": { "hashes": [ "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", @@ -136,6 +194,44 @@ "markers": "python_full_version >= '3.7.0'", "version": "==3.3.2" }, + "cryptography": { + "hashes": [ + "sha256:013629ae70b40af70c9a7a5db40abe5d9054e6f4380e50ce769947b73bf3caad", + "sha256:2346b911eb349ab547076f47f2e035fc8ff2c02380a7cbbf8d87114fa0f1c583", + "sha256:2f66d9cd9147ee495a8374a45ca445819f8929a3efcd2e3df6428e46c3cbb10b", + "sha256:2f88d197e66c65be5e42cd72e5c18afbfae3f741742070e3019ac8f4ac57262c", + "sha256:31f721658a29331f895a5a54e7e82075554ccfb8b163a18719d342f5ffe5ecb1", + "sha256:343728aac38decfdeecf55ecab3264b015be68fc2816ca800db649607aeee648", + "sha256:5226d5d21ab681f432a9c1cf8b658c0cb02533eece706b155e5fbd8a0cdd3949", + "sha256:57080dee41209e556a9a4ce60d229244f7a66ef52750f813bfbe18959770cfba", + "sha256:5a94eccb2a81a309806027e1670a358b99b8fe8bfe9f8d329f27d72c094dde8c", + "sha256:6b7c4f03ce01afd3b76cf69a5455caa9cfa3de8c8f493e0d3ab7d20611c8dae9", + "sha256:7016f837e15b0a1c119d27ecd89b3515f01f90a8615ed5e9427e30d9cdbfed3d", + "sha256:81884c4d096c272f00aeb1f11cf62ccd39763581645b0812e99a91505fa48e0c", + "sha256:81d8a521705787afe7a18d5bfb47ea9d9cc068206270aad0b96a725022e18d2e", + "sha256:8d09d05439ce7baa8e9e95b07ec5b6c886f548deb7e0f69ef25f64b3bce842f2", + "sha256:961e61cefdcb06e0c6d7e3a1b22ebe8b996eb2bf50614e89384be54c48c6b63d", + "sha256:9c0c1716c8447ee7dbf08d6db2e5c41c688544c61074b54fc4564196f55c25a7", + "sha256:a0608251135d0e03111152e41f0cc2392d1e74e35703960d4190b2e0f4ca9c70", + "sha256:a0c5b2b0585b6af82d7e385f55a8bc568abff8923af147ee3c07bd8b42cda8b2", + "sha256:ad803773e9df0b92e0a817d22fd8a3675493f690b96130a5e24f1b8fabbea9c7", + "sha256:b297f90c5723d04bcc8265fc2a0f86d4ea2e0f7ab4b6994459548d3a6b992a14", + "sha256:ba4f0a211697362e89ad822e667d8d340b4d8d55fae72cdd619389fb5912eefe", + "sha256:c4783183f7cb757b73b2ae9aed6599b96338eb957233c58ca8f49a49cc32fd5e", + "sha256:c9bb2ae11bfbab395bdd072985abde58ea9860ed84e59dbc0463a5d0159f5b71", + "sha256:cafb92b2bc622cd1aa6a1dce4b93307792633f4c5fe1f46c6b97cf67073ec961", + "sha256:d45b940883a03e19e944456a558b67a41160e367a719833c53de6911cabba2b7", + "sha256:dc0fdf6787f37b1c6b08e6dfc892d9d068b5bdb671198c72072828b80bd5fe4c", + "sha256:dea567d1b0e8bc5764b9443858b673b734100c2871dc93163f58c46a97a83d28", + "sha256:dec9b018df185f08483f294cae6ccac29e7a6e0678996587363dc352dc65c842", + "sha256:e3ec3672626e1b9e55afd0df6d774ff0e953452886e06e0f1eb7eb0c832e8902", + "sha256:e599b53fd95357d92304510fb7bda8523ed1f79ca98dce2f43c115950aa78801", + "sha256:fa76fbb7596cc5839320000cdd5d0955313696d9511debab7ee7278fc8b5c84a", + "sha256:fff12c88a672ab9c9c1cf7b0c80e3ad9e2ebd9d828d955c126be4fd3e5578c9e" + ], + "markers": "python_version >= '3.7'", + "version": "==42.0.8" + }, "django": { "hashes": [ "sha256:8363ac062bb4ef7c3f12d078f6fa5d154031d129a15170a1066412af49d30905", @@ -145,6 +241,16 @@ "markers": "python_version >= '3.10'", "version": "==5.0.6" }, + "django-allauth": { + "extras": [ + "socialaccount" + ], + "hashes": [ + "sha256:2374164c468a309e6badf70bc3405136df6036f24a20a13387f2a063066bdaa9" + ], + "markers": "python_version >= '3.7'", + "version": "==0.63.3" + }, "django-cors-headers": { "hashes": [ "sha256:5c6e3b7fe870876a1efdfeb4f433782c3524078fa0dc9e0195f6706ce7a242f6", @@ -285,6 +391,14 @@ "markers": "python_version >= '3.5'", "version": "==3.7" }, + "oauthlib": { + "hashes": [ + "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca", + "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918" + ], + "markers": "python_version >= '3.6'", + "version": "==3.2.2" + }, "proto-plus": { "hashes": [ "sha256:30b72a5ecafe4406b0d339db35b56c4059064e69227b8c3bda7462397f966445", @@ -405,6 +519,24 @@ "markers": "python_version >= '3.8'", "version": "==0.4.0" }, + "pycparser": { + "hashes": [ + "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", + "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc" + ], + "markers": "python_version >= '3.8'", + "version": "==2.22" + }, + "pyjwt": { + "extras": [ + "crypto" + ], + "hashes": [ + "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de", + "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320" + ], + "version": "==2.8.0" + }, "requests": { "hashes": [ "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", @@ -413,6 +545,13 @@ "markers": "python_version >= '3.8'", "version": "==2.32.3" }, + "requests-oauthlib": { + "hashes": [ + "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36", + "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9" + ], + "version": "==2.0.0" + }, "rsa": { "hashes": [ "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7", diff --git a/api/README.md b/api/README.md index c026d51..7621fee 100644 --- a/api/README.md +++ b/api/README.md @@ -44,3 +44,8 @@ Bump the version in (variables.tf)[../infra/app/variables.tf] and merge PR. The ## Learn More [Tech Spec](docs/tech_spec.md) + + +## Social Auth + +http://localhost:8000/accounts/google/login/?process=login diff --git a/api/api/admin.py b/api/api/admin.py new file mode 100644 index 0000000..2757cda --- /dev/null +++ b/api/api/admin.py @@ -0,0 +1,6 @@ +from django.contrib import admin + +from api import models + + +admin.site.register(models.Generation) diff --git a/api/api/urls.py b/api/api/urls.py index ead4bd4..fc9d68d 100644 --- a/api/api/urls.py +++ b/api/api/urls.py @@ -5,4 +5,5 @@ urlpatterns = [ path("art", views.art, name="art"), path("health", views.health, name="health"), + path("me", views.me, name="me"), ] diff --git a/api/api/views.py b/api/api/views.py index 32f8589..6cfe1e9 100644 --- a/api/api/views.py +++ b/api/api/views.py @@ -16,3 +16,14 @@ def art(request): # Return a list of all the requested generation's art metadata. return JsonResponse(art_storage.ArtStorage().get_art(gen)) + + +def me(request): + ret = {} + if request.user.is_authenticated: + ret.update({ + 'username': request.user.get_username(), + 'name': request.user.first_name, + 'email': request.user.email, + }) + return JsonResponse(ret) diff --git a/api/app/settings.py b/api/app/settings.py index 10a9c3a..7221012 100644 --- a/api/app/settings.py +++ b/api/app/settings.py @@ -46,6 +46,7 @@ "http://localhost:3000", "http://127.0.0.1:3000", ] +CORS_ALLOW_CREDENTIALS = True # Application definition @@ -59,8 +60,12 @@ 'django.contrib.staticfiles', 'corsheaders', - 'api', + + 'allauth', + 'allauth.account', + 'allauth.socialaccount', + 'allauth.socialaccount.providers.google', ] MIDDLEWARE = [ @@ -71,6 +76,9 @@ #'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', + + 'allauth.account.middleware.AccountMiddleware', + #'django.middleware.clickjacking.XFrameOptionsMiddleware', ] @@ -147,3 +155,38 @@ # https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + + +# Social Auth with allauth + +AUTHENTICATION_BACKENDS = [ + # Needed to login by username in Django admin, regardless of `allauth` + 'django.contrib.auth.backends.ModelBackend', + + # `allauth` specific authentication methods, such as login by email + 'allauth.account.auth_backends.AuthenticationBackend', +] + +# Provider specific settings +SOCIALACCOUNT_PROVIDERS = { + 'google': { + # For each OAuth based provider, either add a ``SocialApp`` + # (``socialaccount`` app) containing the required client + # credentials, or list them here: + 'APP': { + 'client_id': os.environ['GOOGLE_OAUTH2_KEY'], + 'secret': os.environ['GOOGLE_OAUTH2_SECRET'], + 'key': '', + }, + 'SCOPE': [ + 'profile', + 'email', + ], + 'AUTH_PARAMS': { + 'access_type': 'online', + }, + }, +} + +SOCIALACCOUNT_ONLY = True +ACCOUNT_EMAIL_VERIFICATION = 'none' diff --git a/api/app/urls.py b/api/app/urls.py index 7399288..5e7f87f 100644 --- a/api/app/urls.py +++ b/api/app/urls.py @@ -20,6 +20,10 @@ urlpatterns = [ # Local endpoints. path("api/", include("api.urls")), + # Django built-ins path("admin/", admin.site.urls), + + # Social Auth + path('accounts/', include('allauth.urls')), ] diff --git a/infra/app/app.tf b/infra/app/app.tf index 16b759c..df9ffe9 100644 --- a/infra/app/app.tf +++ b/infra/app/app.tf @@ -111,6 +111,25 @@ resource "google_cloud_run_v2_service" "api" { } } } + env { + name = "GOOGLE_OAUTH2_KEY" + value_source { + secret_key_ref { + secret = "google-oauth-key" + version = "latest" + } + } + } + env { + name = "GOOGLE_OAUTH2_SECRET" + value_source { + secret_key_ref { + secret = "google-oauth-secret" + version = "latest" + } + } + } + volume_mounts { name = "cloudsql" mount_path = "/cloudsql" @@ -131,7 +150,7 @@ resource "google_cloud_run_service_iam_binding" "api" { ] } -## DB things. +## Secrets and access. resource "google_secret_manager_secret_iam_member" "storage-db-api" { secret_id = google_secret_manager_secret.storageapi-db-pass.id @@ -139,6 +158,24 @@ resource "google_secret_manager_secret_iam_member" "storage-db-api" { member = "serviceAccount:${google_service_account.api.email}" } +resource "google_secret_manager_secret" "api-secrets" { + for_each = toset(["google-oauth-key", "google-oauth-secret"]) + secret_id = each.key + + replication { + auto {} + } +} + +resource "google_secret_manager_secret_iam_member" "api-secrets" { + for_each = google_secret_manager_secret.api-secrets + secret_id = each.value.secret_id + role = "roles/secretmanager.secretAccessor" + member = "serviceAccount:${google_service_account.api.email}" +} + +## DB things. + resource "google_project_iam_member" "cloudsql" { project = var.project role = "roles/cloudsql.client" diff --git a/infra/app/networking.tf b/infra/app/networking.tf index 85c71d4..d645154 100644 --- a/infra/app/networking.tf +++ b/infra/app/networking.tf @@ -196,6 +196,12 @@ resource "google_compute_url_map" "default" { path_matcher { name = "api" default_service = google_compute_backend_service.api.id + + path_rule { + paths = ["/accounts/google/*"] + service = google_compute_backend_service.api.id + } + path_rule { paths = ["/*"] service = google_compute_backend_service.api.id @@ -205,5 +211,6 @@ resource "google_compute_url_map" "default" { } } } + } } diff --git a/infra/app/variables.tf b/infra/app/variables.tf index 1e9a0d0..c0d3f93 100644 --- a/infra/app/variables.tf +++ b/infra/app/variables.tf @@ -2,7 +2,7 @@ variable "app_versions" { type = map(string) default = { client : "0.2.2", - api : "0.1.1", + api : "0.2.0", } }