diff --git a/docker-compose.yml b/docker-compose.yml
index e7a5276..a4acf52 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -38,6 +38,7 @@ services:
- "9001:9001"
environment:
- DB_REMOTE=False
+ - APP_URL=
frontend:
container_name: ctk-frontend
diff --git a/services/backend/requirements.txt b/services/backend/requirements.txt
index 8a0793f..52b8034 100644
--- a/services/backend/requirements.txt
+++ b/services/backend/requirements.txt
@@ -3,6 +3,7 @@ django-cors-headers==3.11.0
django-axes==5.32.0
djangorestframework==3.13.1
djangorestframework-simplejwt==5.1.0
+django-storages==1.13.1
drf-extensions==0.7.1
dj-rest-auth[with_social]==2.2.4
diff --git a/services/backend/src/api/routing.py b/services/backend/src/api/routing.py
index 27f2082..72e9f02 100644
--- a/services/backend/src/api/routing.py
+++ b/services/backend/src/api/routing.py
@@ -2,7 +2,7 @@ from django.urls import include, path
from rest_framework_extensions.routers import ExtendedDefaultRouter
-from .views import project, generate, user, view
+from .views import project, generate, user, view, auth
class DefaultRouterPlusPlus(ExtendedDefaultRouter):
@@ -20,5 +20,6 @@ api_urls = [
path("generate/", generate.GenerateGenericAPIView.as_view()),
path("auth/self/", user.UserGenericAPIView.as_view()),
path("auth/", include("dj_rest_auth.urls")),
+ path("auth/github/", auth.GitHubLogin.as_view(), name="github_login"),
path("auth/registration/", include("dj_rest_auth.registration.urls")),
]
diff --git a/services/backend/src/api/views/auth.py b/services/backend/src/api/views/auth.py
new file mode 100644
index 0000000..55b69d5
--- /dev/null
+++ b/services/backend/src/api/views/auth.py
@@ -0,0 +1,11 @@
+import os
+from allauth.socialaccount.providers.github.views import GitHubOAuth2Adapter
+from allauth.socialaccount.providers.oauth2.client import OAuth2Client
+from dj_rest_auth.registration.views import SocialLoginView
+
+APP_URL = os.environ.get("APP_URL", "")
+
+class GitHubLogin(SocialLoginView):
+ adapter_class = GitHubOAuth2Adapter
+ callback_url = f"{APP_URL}/github/cb"
+ client_class = OAuth2Client
diff --git a/services/backend/src/main/settings.py b/services/backend/src/main/settings.py
index 266f752..3e71290 100644
--- a/services/backend/src/main/settings.py
+++ b/services/backend/src/main/settings.py
@@ -52,7 +52,9 @@ INSTALLED_APPS = [
"allauth",
"allauth.account",
"allauth.socialaccount",
+ "allauth.socialaccount.providers.github",
"dj_rest_auth.registration",
+ "storages",
"corsheaders",
"axes",
"organizations",
@@ -152,13 +154,6 @@ USE_I18N = True
USE_TZ = True
-# Static files (CSS, JavaScript, Images)
-# https://docs.djangoproject.com/en/4.0/howto/static-files/
-
-PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__))
-STATIC_URL = '/static/'
-STATIC_ROOT = os.path.join(PROJECT_ROOT, 'static')
-
# Default primary key field type
# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field
@@ -187,6 +182,29 @@ if DEBUG:
"rest_framework.renderers.BrowsableAPIRenderer"
)
+# aws
+AWS_STORAGE_BUCKET_NAME = os.getenv('AWS_STORAGE_BUCKET_NAME', None)
+AWS_S3_FILE_OVERWRITE = True
+AWS_S3_CUSTOM_DOMAIN = f'{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com'
+AWS_S3_OBJECT_PARAMETERS = {
+ 'CacheControl': 'max-age=86400',
+}
+AWS_DEFAULT_REGION = os.environ.get(
+ 'AWS_DEFAULT_REGION',
+ 'us-east-1'
+)
+AWS_LOCATION = 'static'
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/4.0/howto/static-files/
+
+if AWS_STORAGE_BUCKET_NAME:
+ STATIC_URL = f'https://{AWS_S3_CUSTOM_DOMAIN}/{AWS_LOCATION}/'
+ STATICFILES_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
+else:
+ STATIC_URL = '/static/'
+ PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__))
+ STATIC_ROOT = os.path.join(PROJECT_ROOT, 'static')
# allauth
ACCOUNT_EMAIL_VERIFICATION = "none"
diff --git a/services/frontend/src/App.tsx b/services/frontend/src/App.tsx
index a45acfc..4e6d65b 100644
--- a/services/frontend/src/App.tsx
+++ b/services/frontend/src/App.tsx
@@ -18,6 +18,7 @@ import Project from "./components/Project";
import Profile from "./components/Profile";
import Signup from "./components/Auth/Signup";
import Login from "./components/Auth/Login";
+import GitHub from "./components/Auth/GitHub";
import { ProtectedRouteProps } from "./partials/ProtectedRoute";
import ProtectedRoute from "./partials/ProtectedRoute";
@@ -131,6 +132,7 @@ export default function App() {
} />
} />
+ } />
diff --git a/services/frontend/src/components/Auth/GitHub/LoginBtn.tsx b/services/frontend/src/components/Auth/GitHub/LoginBtn.tsx
new file mode 100644
index 0000000..cc1fd99
--- /dev/null
+++ b/services/frontend/src/components/Auth/GitHub/LoginBtn.tsx
@@ -0,0 +1,33 @@
+import {
+ REACT_APP_GITHUB_CLIENT_ID,
+ REACT_APP_GITHUB_SCOPE
+} from "../../../constants";
+
+const LoginBtn = () => {
+ return (
+
+ );
+};
+
+export default LoginBtn;
diff --git a/services/frontend/src/components/Auth/GitHub/index.tsx b/services/frontend/src/components/Auth/GitHub/index.tsx
new file mode 100644
index 0000000..5e0562b
--- /dev/null
+++ b/services/frontend/src/components/Auth/GitHub/index.tsx
@@ -0,0 +1,71 @@
+import { useEffect, useState } from "react";
+import { useNavigate, useSearchParams } from "react-router-dom";
+import { LOCAL_STORAGE } from "../../../constants";
+import { toaster } from "../../../utils";
+import { socialAuth } from "../../../hooks/useSocialAuth";
+import { authLoginSuccess } from "../../../reducers";
+import Spinner from "../../global/Spinner";
+
+interface IGitHubProps {
+ dispatch: any;
+}
+
+const GitHub = (props: IGitHubProps) => {
+ const navigate = useNavigate();
+ const { dispatch } = props;
+ const [searchParams] = useSearchParams();
+ const [loading, setLoading] = useState(false);
+ const code = searchParams.get("code");
+
+ useEffect(() => {
+ if (code) {
+ setLoading(true);
+ socialAuth(code)
+ .then((data: any) => {
+ localStorage.setItem(
+ LOCAL_STORAGE,
+ JSON.stringify({
+ access_token: data.access_token,
+ refresh_token: data.refresh_token
+ })
+ );
+ dispatch(authLoginSuccess(data));
+ navigate("/");
+ })
+ .catch(() => {
+ localStorage.removeItem(LOCAL_STORAGE);
+ navigate(`/login`);
+ toaster(`Something went wrong! Session may have expired.`, "error");
+ })
+ .finally(() => {
+ setLoading(false);
+ });
+ } else {
+ navigate(`/login`);
+ }
+ }, [code]);
+
+ return (
+
+
+ {loading && (
+
+
+ logging in...
+
+ )}
+
+
+ );
+};
+
+export default GitHub;
diff --git a/services/frontend/src/components/Auth/Login/index.tsx b/services/frontend/src/components/Auth/Login/index.tsx
index dc75020..45adf81 100644
--- a/services/frontend/src/components/Auth/Login/index.tsx
+++ b/services/frontend/src/components/Auth/Login/index.tsx
@@ -1,12 +1,17 @@
import { useState } from "react";
import { useFormik } from "formik";
import { Link, useNavigate } from "react-router-dom";
+import {
+ REACT_APP_GITHUB_CLIENT_ID,
+ REACT_APP_GITHUB_SCOPE
+} from "../../../constants";
import Spinner from "../../../components/global/Spinner";
import { toaster } from "../../../utils";
import { checkHttpStatus } from "../../../services/helpers";
import { logIn } from "../../../services/auth";
import { LOCAL_STORAGE } from "../../../constants";
import { authLoginSuccess } from "../../../reducers";
+import LoginBtn from "../GitHub/LoginBtn";
interface IProfileProps {
dispatch: any;
@@ -62,7 +67,7 @@ const Login = (props: IProfileProps) => {
<>
-
+
Sign in
+
+
+
+
+
+ Or login with
+
+
+
+ {REACT_APP_GITHUB_SCOPE && REACT_APP_GITHUB_CLIENT_ID && (
+
+ )}
+
>
diff --git a/services/frontend/src/components/Auth/Signup/index.tsx b/services/frontend/src/components/Auth/Signup/index.tsx
index 5267886..21796fd 100644
--- a/services/frontend/src/components/Auth/Signup/index.tsx
+++ b/services/frontend/src/components/Auth/Signup/index.tsx
@@ -5,8 +5,13 @@ import Spinner from "../../../components/global/Spinner";
import { toaster } from "../../../utils";
import { checkHttpStatus } from "../../../services/helpers";
import { signup } from "../../../services/auth";
-import { LOCAL_STORAGE } from "../../../constants";
+import {
+ LOCAL_STORAGE,
+ REACT_APP_GITHUB_CLIENT_ID,
+ REACT_APP_GITHUB_SCOPE
+} from "../../../constants";
import { authLoginSuccess } from "../../../reducers";
+import LoginBtn from "../GitHub/LoginBtn";
interface IProfileProps {
dispatch: any;
@@ -220,6 +225,19 @@ const Signup = (props: IProfileProps) => {
Already have an account?
+
+
+
+
+
+ Or signup with
+
+
+
+ {REACT_APP_GITHUB_SCOPE && REACT_APP_GITHUB_CLIENT_ID && (
+
+ )}
+
>
diff --git a/services/frontend/src/components/Modal/import/index.tsx b/services/frontend/src/components/Modal/import/index.tsx
index 97ecf9e..0e959ae 100644
--- a/services/frontend/src/components/Modal/import/index.tsx
+++ b/services/frontend/src/components/Modal/import/index.tsx
@@ -1,5 +1,5 @@
-import { useCallback, useMemo, useState } from "react";
-import { Field, Formik } from "formik";
+import { useCallback, useMemo } from "react";
+import { Formik } from "formik";
import { styled } from "@mui/joy";
import { XIcon } from "@heroicons/react/outline";
import { CallbackFunction } from "../../../types";
@@ -13,7 +13,6 @@ import TextField from "../../global/FormElements/TextField";
import { toaster } from "../../../utils";
import { reportErrorsAndSubmit } from "../../../utils/forms";
import { ScrollView } from "../../ScrollView";
-import lodash from "lodash";
interface IModalImportProps {
onHide: CallbackFunction;
diff --git a/services/frontend/src/constants/index.ts b/services/frontend/src/constants/index.ts
index c3be7e5..0ca9c77 100644
--- a/services/frontend/src/constants/index.ts
+++ b/services/frontend/src/constants/index.ts
@@ -1,3 +1,6 @@
export const API_SERVER_URL = process.env.REACT_APP_API_SERVER;
+export const REACT_APP_GITHUB_CLIENT_ID =
+ process.env.REACT_APP_GITHUB_CLIENT_ID;
+export const REACT_APP_GITHUB_SCOPE = process.env.REACT_APP_GITHUB_SCOPE;
export const PROJECTS_FETCH_LIMIT = 300;
export const LOCAL_STORAGE = "CtkLocalStorage";
diff --git a/services/frontend/src/hooks/useSocialAuth.ts b/services/frontend/src/hooks/useSocialAuth.ts
new file mode 100644
index 0000000..7739bae
--- /dev/null
+++ b/services/frontend/src/hooks/useSocialAuth.ts
@@ -0,0 +1,16 @@
+import axios from "axios";
+import { API_SERVER_URL } from "../constants";
+
+export const socialAuth = async (code: string) => {
+ const response = await axios({
+ method: "post",
+ url: `${API_SERVER_URL}/auth/github/`,
+ data: {
+ code: code
+ },
+ headers: {
+ "Content-Type": "application/json"
+ }
+ });
+ return response.data;
+};