Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DBC22-1283 Adding basic BCEID login #234

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ REDIS_PORT=<6379/other port of database server>
NODE_ENV=<development/test/production>
NODE_OPTIONS=<openssl-legacy-provider>

# If on Windows, enable for hot reload for react changes
#WATCHPACK_POLLING=true
# same thing on Mac
#CHOKIDAR_USEPOLLING=true

# React
REACT_APP_API_HOST=<localhost:8000/other hostname:port or IP:port of Django server>
REACT_APP_BASE_MAP=<base map wms url>
Expand Down
2 changes: 1 addition & 1 deletion compose/backend/start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ echo 'creating superuser done; starting service'
# python manage.py runserver 0.0.0.0:8000
#trap : TERM INT; sleep 9999999999d & wait
export DJANGO_SETTINGS_MODULE=config.settings
gunicorn -b 0.0.0.0 config.wsgi 2> /tmp/gunicorn.log
gunicorn -b 0.0.0.0 --reload config.wsgi 2> /tmp/gunicorn.log
10 changes: 10 additions & 0 deletions src/backend/apps/authentication/adapters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from django.conf import settings
from allauth.account.adapter import DefaultAccountAdapter

class AccountAdapter(DefaultAccountAdapter):

def get_login_redirect_url(self, request):
return settings.FRONTEND_BASE_URL

def get_logout_redirect_url(self, request):
return settings.FRONTEND_BASE_URL
10 changes: 10 additions & 0 deletions src/backend/apps/authentication/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# from django.urls import include, path

# from .views import login, logout, status, profile

urlpatterns = [
# path('login', login),
# path('logout', logout),
# path('status', status),
# path('profile/', profile)
]
28 changes: 28 additions & 0 deletions src/backend/apps/authentication/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from django.contrib.auth import login as create_session, logout as destroy_session
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse
from django.shortcuts import redirect

from .models import DriveBCUser

def login(request):
user = DriveBCUser.objects.get(username='admin')
create_session(request, user)
return redirect('//localhost:3000/')


def status(request):
if request.user.is_authenticated:
return HttpResponse('logged in')

return HttpResponse('logged out')


def logout(request):
destroy_session(request)

return redirect('//localhost:3000/')


def profile(request):
return redirect('//localhost:3000/')
4 changes: 4 additions & 0 deletions src/backend/apps/shared/api.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from apps.cms.urls import cms_api_router, wagtail_api_router
from django.urls import include, path

from .views import session

urlpatterns = [
# App
path("webcams/", include("apps.webcam.urls")),
Expand All @@ -12,4 +14,6 @@
# CMS
path("wagtail/", wagtail_api_router.urls),
path("cms/", include(cms_api_router.urls)),

path("session", session.as_view()),
]
10 changes: 10 additions & 0 deletions src/backend/apps/shared/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,16 @@ def get_filtered_queryset(self, geo_filter):
return res


class session(APIView):

def get(self, request, format=None):
if request.user.is_authenticated:
return Response({"username": request.user.username,
"email": request.user.email, })

return Response({"username": None})


class AppCacheTestViewSet(APIView):
"""
Endpoint to allow RPS load testing combination of Django and cache
Expand Down
31 changes: 23 additions & 8 deletions src/backend/config/settings/django.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,24 @@
environ.Env.read_env(BASE_DIR / ".env", overwrite=True)

# Meta
DEBUG = env("DEBUG") == 'True'
DEBUG = env("DEBUG") == "True"
SECRET_KEY = env("SECRET_KEY")
WSGI_APPLICATION = "config.wsgi.application"

# Paths and urls
APPEND_SLASH = True
ROOT_URLCONF = "config.urls"
STATIC_URL = "/django-static/"
STATIC_ROOT = os.path.join(SRC_DIR, 'static')
MEDIA_URL = '/django-media/'
MEDIA_ROOT = os.path.join(SRC_DIR, 'media')
STATIC_ROOT = os.path.join(SRC_DIR, "static")
MEDIA_URL = "/django-media/"
MEDIA_ROOT = os.path.join(SRC_DIR, "media")
FRONTEND_BASE_URL = env("FRONTEND_BASE_URL", default="http://localhost:3000/")

# Security
ALLOWED_HOSTS = env.list("DJANGO_ALLOWED_HOSTS")
CORS_ORIGIN_WHITELIST = env.list("DJANGO_CORS_ORIGIN_WHITELIST")
CORS_ALLOW_HEADERS = default_headers + ("contenttype",)
CORS_ALLOW_CREDENTIALS = True
CSRF_COOKIE_SECURE = env.bool("DJANGO_CSRF_COOKIE_SECURE")
SECURE_SSL_REDIRECT = env.bool("DJANGO_SECURE_SSL_REDIRECT")
SESSION_COOKIE_SECURE = env.bool("DJANGO_SESSION_COOKIE_SECURE")
Expand All @@ -37,9 +39,11 @@
"whitenoise.middleware.WhiteNoiseMiddleware",
"corsheaders.middleware.CorsMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"allauth.usersessions.middleware.UserSessionsMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
# "django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"allauth.account.middleware.AccountMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"wagtail.contrib.redirects.middleware.RedirectMiddleware",
Expand All @@ -63,6 +67,7 @@

# Auth
AUTH_USER_MODEL = "authentication.DriveBCUser"

AUTH_PASSWORD_VALIDATORS = [
{"NAME": f"django.contrib.auth.password_validation.{name}"}
for name in [
Expand All @@ -73,6 +78,14 @@
]
]

AUTHENTICATION_BACKENDS = [
# "oauth2_provider.backends.OAuth2Backend",
"django.contrib.auth.backends.ModelBackend",

# `allauth` specific authentication methods, such as login by email
'allauth.account.auth_backends.AuthenticationBackend',
]

# Language
USE_I18N = False

Expand All @@ -95,15 +108,16 @@
"allauth",
"allauth.account",
"allauth.socialaccount",
"dj_rest_auth",
"dj_rest_auth.registration",
'allauth.socialaccount.providers.openid_connect',
# "dj_rest_auth",
# "dj_rest_auth.registration",
"huey.contrib.djhuey",
"rest_framework",
"rest_framework.authtoken",
"rest_framework_gis",
"django_filters",
"corsheaders",
'wagtail.api.v2',
"wagtail.api.v2",
"wagtail.contrib.forms",
"wagtail.contrib.redirects",
"wagtail.embeds",
Expand Down Expand Up @@ -174,3 +188,4 @@
"level": "WARNING",
},
}

23 changes: 23 additions & 0 deletions src/backend/config/settings/third_party.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,26 @@
# Wagtail
WAGTAIL_SITE_NAME = 'DriveBC'
WAGTAILEMBEDS_RESPONSIVE_HTML = True

# Allauth

# need our own adapter to override various redirect url methods following
# login or logout
ACCOUNT_ADAPTER = 'apps.authentication.adapters.AccountAdapter'

SOCIALACCOUNT_PROVIDERS = {
'openid_connect': {
'SOCIALACCOUNT_EMAIL_VERIFICATION': 'none',
'SOCIALACCOUNT_LOGIN_ON_GET': True,
'APP': {
'provider_id': 'bceid',
'name': 'BCeID via Keycloak',
'client_id': env("BCEID_CLIENT_ID"),
'secret': env("BCEID_SECRET"),
'settings': {
'server_url': env("BCEID_URL"),
}
},
}
}

4 changes: 4 additions & 0 deletions src/backend/config/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,9 @@
# packages
path('drivebc-cms/', include("apps.cms.urls")),

# auth system
path('accounts/', include('allauth.urls')),
path('accounts/', include("apps.authentication.urls")),

# TO BE REMOVED IN PRODUCTION
] + static_override(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
4 changes: 2 additions & 2 deletions src/backend/requirements/base.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
Django==4.2.3
dj-rest-auth[with_social]==4.0.1
django-allauth==0.54.0
django-allauth==0.59.0
django-environ==0.10.0
djangorestframework==3.14.0
djangorestframework-gis==1.0
django-cors-headers==3.13.0
django-filter==23.2
django-oauth-toolkit==2.3.0
whitenoise==6.5.0
httpx==0.24.1
huey==2.4.5
Expand Down
71 changes: 55 additions & 16 deletions src/frontend/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import './App.scss';
// Components and functions
import Header from './Header.js';
import MapPage from './pages/MapPage';
import Modal from './Modal.js';
import AccountPage from './pages/AccountPage';
import CamerasPage from './pages/CamerasPage';
import CameraDetailsPage from './pages/CameraDetailsPage';
import EventsPage from './pages/EventsPage';
Expand All @@ -19,6 +21,9 @@ import BulletinDetailsPage from './pages/BulletinDetailsPage';
import ScrollToTop from './Components/ScrollToTop';

export const MapContext = createContext(null);
export const AuthContext = createContext(null);

let callingSession = false;

function App() {
function getInitialMapContext() {
Expand All @@ -38,25 +43,59 @@ function App() {
};
}

function getInitialAuthContext() {
if (!callingSession) {
callingSession = true;

fetch(`${window.API_HOST}/api/session`, {
headers: { 'Accept': 'application/json' },
credentials: "include",
}).then((response) => response.json())
.then((data) => {
const ret = {
loginStateKnown: true,
}
if (data.username) {
ret.username = data.username;
ret.email = data.email;
}
setAuthContext((prior) => {
if (ret.loginStateKnown != prior.loginStateKnown) { return ret; }
if (ret.username != prior.username) { return ret; }
if (ret.email != prior.email) { return ret; }
return prior;
});
})
.finally(() => callingSession = false);
}

return { loginStateKnown: false }
}

const [mapContext, setMapContext] = useState(getInitialMapContext());
const [authContext, setAuthContext] = useState(getInitialAuthContext());

return (
<MapContext.Provider value={{ mapContext, setMapContext }}>
<div className="App">
<Header />
<ScrollToTop />
<Routes>
<Route path="/" element={<MapPage />} />
<Route path="/cameras" element={<CamerasPage />} />
<Route path="/cameras/:id" element={<CameraDetailsPage />} />
<Route path="/events" element={<EventsPage />} />
<Route path="/advisories" element={<AdvisoriesListPage />} />
<Route path="/advisories/:id" element={<AdvisoryDetailsPage />} />
<Route path="/bulletins" element={<BulletinsListPage />} />
<Route path="/bulletins/:id" element={<BulletinDetailsPage />} />
</Routes>
</div>
</MapContext.Provider>
<AuthContext.Provider value={{ authContext, setAuthContext }}>
<MapContext.Provider value={{ mapContext, setMapContext }}>
<div className="App">
<Header />
<ScrollToTop />
<Routes>
<Route path="/" element={<MapPage />} />
<Route path="/cameras" element={<CamerasPage />} />
<Route path="/cameras/:id" element={<CameraDetailsPage />} />
<Route path="/events" element={<EventsPage />} />
<Route path="/advisories" element={<AdvisoriesListPage />} />
<Route path="/advisories/:id" element={<AdvisoryDetailsPage />} />
<Route path="/bulletins" element={<BulletinsListPage />} />
<Route path="/bulletins/:id" element={<BulletinDetailsPage />} />
<Route path="/account" element={<AccountPage />} />
</Routes>
<Modal />
</div>
</MapContext.Provider>
</AuthContext.Provider>
);
}

Expand Down
Loading
Loading