feat: github auth

pull/104/head
corpulent 2 years ago
parent 64b33b28d5
commit 0e4f9250d4

@ -38,6 +38,7 @@ services:
- "9001:9001"
environment:
- DB_REMOTE=False
- APP_URL=
frontend:
container_name: ctk-frontend

@ -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

@ -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")),
]

@ -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

@ -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"

@ -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() {
<Route path="/signup" element={<Signup dispatch={dispatch} />} />
<Route path="/login" element={<Login dispatch={dispatch} />} />
<Route path="/github/cb" element={<GitHub dispatch={dispatch} />} />
</Routes>
</div>

@ -0,0 +1,33 @@
import {
REACT_APP_GITHUB_CLIENT_ID,
REACT_APP_GITHUB_SCOPE
} from "../../../constants";
const LoginBtn = () => {
return (
<div className="relative flex justify-center text-sm mt-6">
<a
href={`https://github.com/login/oauth/authorize?scope=${REACT_APP_GITHUB_SCOPE}&client_id=${REACT_APP_GITHUB_CLIENT_ID}`}
className="flex flex-row space-x-2 w-full inline-flex justify-center py-2 px-4 border border-gray-800 rounded-md shadow-sm bg-white text-sm font-medium text-gray-800 hover:bg-gray-50"
>
<div>
<svg
className="w-5 h-5"
aria-hidden="true"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
fillRule="evenodd"
d="M10 0C4.477 0 0 4.484 0 10.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0110 4.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.203 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.942.359.31.678.921.678 1.856 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0020 10.017C20 4.484 15.522 0 10 0z"
clipRule="evenodd"
/>
</svg>
</div>
<span>GitHub</span>
</a>
</div>
);
};
export default LoginBtn;

@ -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 (
<div
className="text-center"
style={{
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
minHeight: "calc(60vh - 120px)"
}}
>
<div className="flex">
{loading && (
<div className="flex flex-row items-center space-x-2">
<Spinner className="w-6 h-6 text-blue-600" />
<span className="text-base text-gray-800">logging in...</span>
</div>
)}
</div>
</div>
);
};
export default GitHub;

@ -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) => {
<>
<div className="flex flex-col">
<main className="py-6 md:w-1/3 lg:w-1/4 mx-auto">
<h2 className="mb-4 px-4 sm:px-6 md:flex-row md:px-8 text-xl font-extrabold dark:text-white text-gray-900">
<h2 className="mb-4 px-4 sm:px-6 md:flex-row md:px-8 text-xl font-bold dark:text-white text-gray-900">
Sign in
</h2>
<form autoComplete="off">
@ -152,6 +157,19 @@ const Login = (props: IProfileProps) => {
<span className="text-sm">Create account</span>
</Link>
</div>
<div className="mt-6">
<div className="relative">
<div className="relative flex justify-center text-sm">
<span className="px-2 text-gray-800 font-medium">
Or login with
</span>
</div>
</div>
{REACT_APP_GITHUB_SCOPE && REACT_APP_GITHUB_CLIENT_ID && (
<LoginBtn />
)}
</div>
</main>
</div>
</>

@ -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) => {
<span className="text-sm">Already have an account?</span>
</Link>
</div>
<div className="mt-6">
<div className="relative">
<div className="relative flex justify-center text-sm">
<span className="px-2 text-gray-800 font-medium">
Or signup with
</span>
</div>
</div>
{REACT_APP_GITHUB_SCOPE && REACT_APP_GITHUB_CLIENT_ID && (
<LoginBtn />
)}
</div>
</main>
</div>
</>

@ -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;

@ -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";

@ -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;
};
Loading…
Cancel
Save