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

Auth Migration, Calendar and Other Misc. Fixes #406

Merged
merged 28 commits into from
Dec 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
d7baf69
Bump @sveltejs/kit
dependabot[bot] Nov 25, 2024
9bc20be
Initial migration to new session based auth system with AllAuth
seanmorley15 Nov 29, 2024
f119e6f
Refactor settings.py to remove unused authentication configurations a…
seanmorley15 Nov 29, 2024
b86c725
Enhance admin security by integrating secure_admin_login from AllAuth…
seanmorley15 Nov 29, 2024
c65fcc2
Migrate to session based auth
seanmorley15 Nov 29, 2024
84566b8
User profile settings API and remove old Dj-Rest-Auth code
seanmorley15 Nov 30, 2024
50dc042
Refactor user serializers, update Docker configurations, and remove u…
seanmorley15 Dec 1, 2024
4ecad7f
Update nl.json
seanmorley15 Dec 2, 2024
dc29f48
Add 'Northern Lights' theme and update localization and Tailwind conf…
seanmorley15 Dec 2, 2024
21d8b37
Merge pull request #395 from seanmorley15/update-nl
seanmorley15 Dec 2, 2024
88dbd41
Merge pull request #375 from seanmorley15/dependabot/npm_and_yarn/fro…
seanmorley15 Dec 3, 2024
a39e22b
Add new dashboard
seanmorley15 Dec 3, 2024
d44cb06
Refactor AdventureCard usage and integrate event calendar components
seanmorley15 Dec 4, 2024
6410580
Add calendar route and integrate calendar component in Navbar
seanmorley15 Dec 4, 2024
6a00a2e
Refactor Docker Compose configuration and enhance email management in…
seanmorley15 Dec 7, 2024
0272c6b
Refactor user admin settings and enhance email management functionality
seanmorley15 Dec 12, 2024
2ccbf4b
Update email verification and password reset flows; refactor Docker C…
seanmorley15 Dec 12, 2024
54d7a1a
Update forgot password link to point to user reset-password route
seanmorley15 Dec 12, 2024
673a56c
Add MFA to login screen
seanmorley15 Dec 12, 2024
7b7db1c
Refactor email management and localization; update requirements and s…
seanmorley15 Dec 13, 2024
1b54f8e
Implement TOTP 2FA modal; add QR code generation and recovery codes m…
seanmorley15 Dec 13, 2024
9bf0849
Add multi-factor authentication (MFA) support; update localization an…
seanmorley15 Dec 14, 2024
0376709
Enhance localization support; update error messages and add translati…
seanmorley15 Dec 14, 2024
c0fd91e
Add emoji picker to category dropdown; implement toggle functionality…
seanmorley15 Dec 14, 2024
4839edd
Update password reset form and add more localization for error messages
seanmorley15 Dec 14, 2024
0c27f4b
Add download adventures as ICS calendar
seanmorley15 Dec 14, 2024
c966b73
Fix formatting in README.md for better readability
seanmorley15 Dec 14, 2024
10dbafd
Update button label in NewTransportation component to Create
seanmorley15 Dec 14, 2024
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
31 changes: 15 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@

[!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/seanmorley15)

- **[Documentation](https://adventurelog.app)**
- **[Demo](https://demo.adventurelog.app)**
- **[Join the AdventureLog Community Discord Server](https://discord.gg/wRbQ9Egr8C)**

- **[Documentation](https://adventurelog.app)**
- **[Demo](https://demo.adventurelog.app)**
- **[Join the AdventureLog Community Discord Server](https://discord.gg/wRbQ9Egr8C)**

# Table of Contents

Expand Down Expand Up @@ -59,18 +58,18 @@ Here is a summary of the configuration options available in the `docker-compose.

### Backend Container (server)

| Name | Required | Description | Default Value |
| ----------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- |
| `PGHOST` | Yes | Databse host. | db |
| `PGDATABASE` | Yes | Database. | database |
| `PGUSER` | Yes | Database user. | adventure |
| `PGPASSWORD` | Yes | Database password. | changeme123 |
| `DJANGO_ADMIN_USERNAME` | Yes | Default username. | admin |
| `DJANGO_ADMIN_PASSWORD` | Yes | Default password, change after inital login. | admin |
| `DJANGO_ADMIN_EMAIL` | Yes | Default user's email. | [email protected] |
| `PUBLIC_URL` | Yes | This needs to match the outward port of the server and be accessible from where the app is used. It is used for the creation of image urls. | 'http://localhost:8016' |
| `CSRF_TRUSTED_ORIGINS` | Yes | Need to be changed to the orgins where you use your backend server and frontend. These values are comma seperated. | http://localhost:8016 |
| `FRONTEND_URL` | Yes | This is the publicly accessible url to the **frontend** container. This link should be accessible for all users. Used for email generation. | 'http://localhost:8015' |
| Name | Required | Description | Default Value |
| ----------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- |
| `PGHOST` | Yes | Databse host. | db |
| `PGDATABASE` | Yes | Database. | database |
| `PGUSER` | Yes | Database user. | adventure |
| `PGPASSWORD` | Yes | Database password. | changeme123 |
| `DJANGO_ADMIN_USERNAME` | Yes | Default username. | admin |
| `DJANGO_ADMIN_PASSWORD` | Yes | Default password, change after inital login. | admin |
| `DJANGO_ADMIN_EMAIL` | Yes | Default user's email. | [email protected] |
| `PUBLIC_URL` | Yes | This needs to match the outward port of the server and be accessible from where the app is used. It is used for the creation of image urls. | http://localhost:8016 |
| `CSRF_TRUSTED_ORIGINS` | Yes | Need to be changed to the orgins where you use your backend server and frontend. These values are comma seperated. | http://localhost:8016 |
| `FRONTEND_URL` | Yes | This is the publicly accessible url to the **frontend** container. This link should be accessible for all users. Used for email generation. | http://localhost:8015 |

## Running the Containers

Expand Down
27 changes: 0 additions & 27 deletions backend/LICENSE

This file was deleted.

4 changes: 0 additions & 4 deletions backend/README.md

This file was deleted.

9 changes: 7 additions & 2 deletions backend/server/adventures/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
from django.utils.html import mark_safe
from .models import Adventure, Checklist, ChecklistItem, Collection, Transportation, Note, AdventureImage, Visit, Category
from worldtravel.models import Country, Region, VisitedRegion
from allauth.account.decorators import secure_admin_login

admin.autodiscover()
admin.site.login = secure_admin_login(admin.site.login)



class AdventureAdmin(admin.ModelAdmin):
Expand Down Expand Up @@ -54,9 +59,9 @@ def number_of_visits(self, obj):

class CustomUserAdmin(UserAdmin):
model = CustomUser
list_display = ['username', 'email', 'is_staff', 'is_active', 'image_display']
list_display = ['username', 'is_staff', 'is_active', 'image_display']
readonly_fields = ('uuid',)
search_fields = ('username', 'email')
search_fields = ('username',)
fieldsets = UserAdmin.fieldsets + (
(None, {'fields': ('profile_pic', 'uuid', 'public_profile')}),
)
Expand Down
10 changes: 10 additions & 0 deletions backend/server/adventures/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,13 @@ def __call__(self, request):
response['X-AdventureLog-Version'] = '1.0.0'

return response

# make a middlewra that prints all of the request cookies
class PrintCookiesMiddleware:
def __init__(self, get_response):
self.get_response = get_response

def __call__(self, request):
print(request.COOKIES)
response = self.get_response(request)
return response
3 changes: 2 additions & 1 deletion backend/server/adventures/urls.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from django.urls import include, path
from rest_framework.routers import DefaultRouter
from .views import AdventureViewSet, ChecklistViewSet, CollectionViewSet, NoteViewSet, StatsViewSet, GenerateDescription, ActivityTypesView, TransportationViewSet, AdventureImageViewSet, ReverseGeocodeViewSet, CategoryViewSet
from .views import AdventureViewSet, ChecklistViewSet, CollectionViewSet, NoteViewSet, StatsViewSet, GenerateDescription, ActivityTypesView, TransportationViewSet, AdventureImageViewSet, ReverseGeocodeViewSet, CategoryViewSet, IcsCalendarGeneratorViewSet

router = DefaultRouter()
router.register(r'adventures', AdventureViewSet, basename='adventures')
Expand All @@ -14,6 +14,7 @@
router.register(r'images', AdventureImageViewSet, basename='images')
router.register(r'reverse-geocode', ReverseGeocodeViewSet, basename='reverse-geocode')
router.register(r'categories', CategoryViewSet, basename='categories')
router.register(r'ics-calendar', IcsCalendarGeneratorViewSet, basename='ics-calendar')


urlpatterns = [
Expand Down
59 changes: 58 additions & 1 deletion backend/server/adventures/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
from django.shortcuts import get_object_or_404
from rest_framework import status
from django.contrib.auth import get_user_model
from icalendar import Calendar, Event, vText, vCalAddress
from django.http import HttpResponse
from datetime import datetime

User = get_user_model()

Expand Down Expand Up @@ -73,6 +76,7 @@ def apply_sorting(self, queryset):
return queryset.order_by(ordering)

def get_queryset(self):
print(self.request.user)
# if the user is not authenticated return only public adventures for retrieve action
if not self.request.user.is_authenticated:
if self.action == 'retrieve':
Expand Down Expand Up @@ -1202,4 +1206,57 @@ def mark_visited_region(self, request):
visited_region.save()
new_region_count += 1
new_regions[region.id] = region.name
return Response({"new_regions": new_region_count, "regions": new_regions})
return Response({"new_regions": new_region_count, "regions": new_regions})


from django.http import HttpResponse
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from icalendar import Calendar, Event, vText, vCalAddress
from datetime import datetime, timedelta

class IcsCalendarGeneratorViewSet(viewsets.ViewSet):
permission_classes = [IsAuthenticated]

@action(detail=False, methods=['get'])
def generate(self, request):
adventures = Adventure.objects.filter(user_id=request.user)
serializer = AdventureSerializer(adventures, many=True)
user = request.user
name = f"{user.first_name} {user.last_name}"
print(serializer.data)

cal = Calendar()
cal.add('prodid', '-//My Adventure Calendar//example.com//')
cal.add('version', '2.0')

for adventure in serializer.data:
if adventure['visits']:
for visit in adventure['visits']:
event = Event()
event.add('summary', adventure['name'])
start_date = datetime.strptime(visit['start_date'], '%Y-%m-%d').date()
end_date = datetime.strptime(visit['end_date'], '%Y-%m-%d').date() + timedelta(days=1) if visit['end_date'] else start_date + timedelta(days=1)
event.add('dtstart', start_date)
event.add('dtend', end_date)
event.add('dtstamp', datetime.now())
event.add('transp', 'TRANSPARENT')
event.add('class', 'PUBLIC')
event.add('created', datetime.now())
event.add('last-modified', datetime.now())
event.add('description', adventure['description'])
if adventure.get('location'):
event.add('location', adventure['location'])
if adventure.get('link'):
event.add('url', adventure['link'])

organizer = vCalAddress(f'MAILTO:{user.email}')
organizer.params['cn'] = vText(name)
event.add('organizer', organizer)

cal.add_component(event)

response = HttpResponse(cal.to_ical(), content_type='text/calendar')
response['Content-Disposition'] = 'attachment; filename=adventures.ics'
return response
77 changes: 37 additions & 40 deletions backend/server/main/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os
from dotenv import load_dotenv
from datetime import timedelta
from os import getenv
from pathlib import Path
# Load environment variables from .env file
Expand All @@ -35,8 +34,6 @@
# ]
ALLOWED_HOSTS = ['*']

# Application definition

INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
Expand All @@ -47,19 +44,19 @@
'django.contrib.sites',
'rest_framework',
'rest_framework.authtoken',
'dj_rest_auth',
'allauth',
'allauth.account',
'dj_rest_auth.registration',
'allauth.mfa',
'allauth.headless',
'allauth.socialaccount',
'allauth.socialaccount.providers.facebook',
# "widget_tweaks",
# "slippers",
'drf_yasg',
'corsheaders',
'adventures',
'worldtravel',
'users',
'django.contrib.gis',

)

MIDDLEWARE = (
Expand All @@ -83,19 +80,13 @@
}
}


# For backwards compatibility for Django 1.8
MIDDLEWARE_CLASSES = MIDDLEWARE

ROOT_URLCONF = 'main.urls'

# WSGI_APPLICATION = 'demo.wsgi.application'

SIMPLE_JWT = {
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=60),
"REFRESH_TOKEN_LIFETIME": timedelta(days=365),
}

# Database
# https://docs.djangoproject.com/en/1.7/ref/settings/#databases

Expand All @@ -114,6 +105,7 @@
}



# Internationalization
# https://docs.djangoproject.com/en/1.7/topics/i18n/

Expand All @@ -127,6 +119,8 @@

USE_TZ = True



# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.7/howto/static-files/

Expand All @@ -139,7 +133,15 @@
MEDIA_ROOT = BASE_DIR / 'media'
STATICFILES_DIRS = [BASE_DIR / 'static']

# TEMPLATE_DIRS = [os.path.join(BASE_DIR, 'templates')]
STORAGES = {
"staticfiles": {
"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage",
},
"default": {
"BACKEND": "django.core.files.storage.FileSystemStorage",
}
}


TEMPLATES = [
{
Expand All @@ -157,32 +159,34 @@
},
]

REST_AUTH = {
'SESSION_LOGIN': True,
'USE_JWT': True,
'JWT_AUTH_COOKIE': 'auth',
'JWT_AUTH_HTTPONLY': False,
'REGISTER_SERIALIZER': 'users.serializers.RegisterSerializer',
'USER_DETAILS_SERIALIZER': 'users.serializers.CustomUserDetailsSerializer',
'PASSWORD_RESET_SERIALIZER': 'users.serializers.MyPasswordResetSerializer'
}
# Authentication settings

DISABLE_REGISTRATION = getenv('DISABLE_REGISTRATION', 'False') == 'True'
DISABLE_REGISTRATION_MESSAGE = getenv('DISABLE_REGISTRATION_MESSAGE', 'Registration is disabled. Please contact the administrator if you need an account.')

STORAGES = {
"staticfiles": {
"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage",
},
"default": {
"BACKEND": "django.core.files.storage.FileSystemStorage",
}
}
ALLAUTH_UI_THEME = "dark"
SILENCED_SYSTEM_CHECKS = ["slippers.E001"]

AUTH_USER_MODEL = 'users.CustomUser'

ACCOUNT_ADAPTER = 'users.adapters.NoNewUsersAccountAdapter'

ACCOUNT_SIGNUP_FORM_CLASS = 'users.form_overrides.CustomSignupForm'

SESSION_SAVE_EVERY_REQUEST = True

FRONTEND_URL = getenv('FRONTEND_URL', 'http://localhost:3000')

HEADLESS_FRONTEND_URLS = {
"account_confirm_email": f"{FRONTEND_URL}/user/verify-email/{{key}}",
"account_reset_password": f"{FRONTEND_URL}/user/reset-password",
"account_reset_password_from_key": f"{FRONTEND_URL}/user/reset-password/{{key}}",
"account_signup": f"{FRONTEND_URL}/signup",
# Fallback in case the state containing the `next` URL is lost and the handshake
# with the third-party provider fails.
"socialaccount_login_error": f"{FRONTEND_URL}/account/provider/callback",
}

EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
SITE_ID = 1
ACCOUNT_EMAIL_REQUIRED = True
Expand Down Expand Up @@ -214,26 +218,20 @@
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication',
'dj_rest_auth.jwt_auth.JWTCookieAuthentication'
),
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
# 'DEFAULT_PERMISSION_CLASSES': [
# 'rest_framework.permissions.IsAuthenticated',
# ],
}

SWAGGER_SETTINGS = {
'LOGIN_URL': 'login',
'LOGOUT_URL': 'logout',
}

# For demo purposes only. Use a white list in the real world.
CORS_ORIGIN_ALLOW_ALL = True
CORS_ALLOWED_ORIGINS = [origin.strip() for origin in getenv('CSRF_TRUSTED_ORIGINS', 'http://localhost').split(',') if origin.strip()]

from os import getenv

CSRF_TRUSTED_ORIGINS = [origin.strip() for origin in getenv('CSRF_TRUSTED_ORIGINS', 'http://localhost').split(',') if origin.strip()]

DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'

LOGGING = {
Expand All @@ -260,6 +258,5 @@
},
},
}

# https://github.com/dr5hn/countries-states-cities-database/tags
COUNTRY_REGION_JSON_VERSION = 'v2.4'
Loading
Loading