mirror of
https://github.com/khoaliber/khoj.git
synced 2026-03-05 13:21:18 +00:00
[Multi-User]: Part 0 - Add support for logging in with Google (#487)
* Add concept of user authentication to the request session via GoogleUser
This commit is contained in:
0
src/database/__init__.py
Normal file
0
src/database/__init__.py
Normal file
78
src/database/adapters/__init__.py
Normal file
78
src/database/adapters/__init__.py
Normal file
@@ -0,0 +1,78 @@
|
||||
from typing import Type, TypeVar
|
||||
import uuid
|
||||
|
||||
from django.db import models
|
||||
from django.contrib.sessions.backends.db import SessionStore
|
||||
|
||||
# Import sync_to_async from Django Channels
|
||||
from asgiref.sync import sync_to_async
|
||||
|
||||
from fastapi import HTTPException
|
||||
|
||||
from database.models import KhojUser, GoogleUser, NotionConfig
|
||||
|
||||
ModelType = TypeVar("ModelType", bound=models.Model)
|
||||
|
||||
|
||||
async def retrieve_object(model_class: Type[ModelType], id: int) -> ModelType:
|
||||
instance = await model_class.objects.filter(id=id).afirst()
|
||||
if not instance:
|
||||
raise HTTPException(status_code=404, detail=f"{model_class.__name__} not found")
|
||||
return instance
|
||||
|
||||
|
||||
async def set_notion_config(token: str, user: KhojUser):
|
||||
notion_config = await NotionConfig.objects.filter(user=user).afirst()
|
||||
if not notion_config:
|
||||
notion_config = await NotionConfig.objects.acreate(token=token, user=user)
|
||||
else:
|
||||
notion_config.token = token
|
||||
await notion_config.asave()
|
||||
return notion_config
|
||||
|
||||
|
||||
async def get_or_create_user(token: dict) -> KhojUser:
|
||||
user = await get_user_by_token(token)
|
||||
if not user:
|
||||
user = await create_google_user(token)
|
||||
return user
|
||||
|
||||
|
||||
async def create_google_user(token: dict) -> KhojUser:
|
||||
user_info = token.get("userinfo")
|
||||
user = await KhojUser.objects.acreate(
|
||||
username=user_info.get("email"), email=user_info.get("email"), uuid=uuid.uuid4()
|
||||
)
|
||||
await user.asave()
|
||||
await GoogleUser.objects.acreate(
|
||||
sub=user_info.get("sub"),
|
||||
azp=user_info.get("azp"),
|
||||
email=user_info.get("email"),
|
||||
name=user_info.get("name"),
|
||||
given_name=user_info.get("given_name"),
|
||||
family_name=user_info.get("family_name"),
|
||||
picture=user_info.get("picture"),
|
||||
locale=user_info.get("locale"),
|
||||
user=user,
|
||||
)
|
||||
|
||||
return user
|
||||
|
||||
|
||||
async def get_user_by_token(token: dict) -> KhojUser:
|
||||
user_info = token.get("userinfo")
|
||||
google_user = await GoogleUser.objects.filter(sub=user_info.get("sub")).select_related("user").afirst()
|
||||
if not google_user:
|
||||
return None
|
||||
return google_user.user
|
||||
|
||||
|
||||
async def retrieve_user(session_id: str) -> KhojUser:
|
||||
session = SessionStore(session_key=session_id)
|
||||
if not await sync_to_async(session.exists)(session_key=session_id):
|
||||
raise HTTPException(status_code=401, detail="Invalid session")
|
||||
session_data = await sync_to_async(session.load)()
|
||||
user = await KhojUser.objects.filter(id=session_data.get("_auth_user_id")).afirst()
|
||||
if not user:
|
||||
raise HTTPException(status_code=401, detail="Invalid user")
|
||||
return user
|
||||
8
src/database/admin.py
Normal file
8
src/database/admin.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.admin import UserAdmin
|
||||
|
||||
# Register your models here.
|
||||
|
||||
from database.models import KhojUser
|
||||
|
||||
admin.site.register(KhojUser, UserAdmin)
|
||||
6
src/database/apps.py
Normal file
6
src/database/apps.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class DatabaseConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "database"
|
||||
98
src/database/migrations/0001_khojuser.py
Normal file
98
src/database/migrations/0001_khojuser.py
Normal file
@@ -0,0 +1,98 @@
|
||||
# Generated by Django 4.2.5 on 2023-09-14 19:00
|
||||
|
||||
import django.contrib.auth.models
|
||||
import django.contrib.auth.validators
|
||||
from django.db import migrations, models
|
||||
import django.utils.timezone
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
("auth", "0012_alter_user_first_name_max_length"),
|
||||
]
|
||||
|
||||
run_before = [
|
||||
("admin", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="KhojUser",
|
||||
fields=[
|
||||
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
|
||||
("password", models.CharField(max_length=128, verbose_name="password")),
|
||||
("last_login", models.DateTimeField(blank=True, null=True, verbose_name="last login")),
|
||||
(
|
||||
"is_superuser",
|
||||
models.BooleanField(
|
||||
default=False,
|
||||
help_text="Designates that this user has all permissions without explicitly assigning them.",
|
||||
verbose_name="superuser status",
|
||||
),
|
||||
),
|
||||
(
|
||||
"username",
|
||||
models.CharField(
|
||||
error_messages={"unique": "A user with that username already exists."},
|
||||
help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
|
||||
max_length=150,
|
||||
unique=True,
|
||||
validators=[django.contrib.auth.validators.UnicodeUsernameValidator()],
|
||||
verbose_name="username",
|
||||
),
|
||||
),
|
||||
("first_name", models.CharField(blank=True, max_length=150, verbose_name="first name")),
|
||||
("last_name", models.CharField(blank=True, max_length=150, verbose_name="last name")),
|
||||
("email", models.EmailField(blank=True, max_length=254, verbose_name="email address")),
|
||||
(
|
||||
"is_staff",
|
||||
models.BooleanField(
|
||||
default=False,
|
||||
help_text="Designates whether the user can log into this admin site.",
|
||||
verbose_name="staff status",
|
||||
),
|
||||
),
|
||||
(
|
||||
"is_active",
|
||||
models.BooleanField(
|
||||
default=True,
|
||||
help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
|
||||
verbose_name="active",
|
||||
),
|
||||
),
|
||||
("date_joined", models.DateTimeField(default=django.utils.timezone.now, verbose_name="date joined")),
|
||||
(
|
||||
"groups",
|
||||
models.ManyToManyField(
|
||||
blank=True,
|
||||
help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
|
||||
related_name="user_set",
|
||||
related_query_name="user",
|
||||
to="auth.group",
|
||||
verbose_name="groups",
|
||||
),
|
||||
),
|
||||
(
|
||||
"user_permissions",
|
||||
models.ManyToManyField(
|
||||
blank=True,
|
||||
help_text="Specific permissions for this user.",
|
||||
related_name="user_set",
|
||||
related_query_name="user",
|
||||
to="auth.permission",
|
||||
verbose_name="user permissions",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "user",
|
||||
"verbose_name_plural": "users",
|
||||
"abstract": False,
|
||||
},
|
||||
managers=[
|
||||
("objects", django.contrib.auth.models.UserManager()),
|
||||
],
|
||||
),
|
||||
]
|
||||
32
src/database/migrations/0002_googleuser.py
Normal file
32
src/database/migrations/0002_googleuser.py
Normal file
@@ -0,0 +1,32 @@
|
||||
# Generated by Django 4.2.4 on 2023-09-18 23:24
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("database", "0001_khojuser"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="GoogleUser",
|
||||
fields=[
|
||||
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
|
||||
("sub", models.CharField(max_length=200)),
|
||||
("azp", models.CharField(max_length=200)),
|
||||
("email", models.CharField(max_length=200)),
|
||||
("name", models.CharField(max_length=200)),
|
||||
("given_name", models.CharField(max_length=200)),
|
||||
("family_name", models.CharField(max_length=200)),
|
||||
("picture", models.CharField(max_length=200)),
|
||||
("locale", models.CharField(max_length=200)),
|
||||
(
|
||||
"user",
|
||||
models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,79 @@
|
||||
# Generated by Django 4.2.5 on 2023-09-27 17:52
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("database", "0002_googleuser"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="Configuration",
|
||||
fields=[
|
||||
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="ConversationProcessorConfig",
|
||||
fields=[
|
||||
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
|
||||
("conversation", models.JSONField()),
|
||||
("enable_offline_chat", models.BooleanField(default=False)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="GithubConfig",
|
||||
fields=[
|
||||
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
|
||||
("pat_token", models.CharField(max_length=200)),
|
||||
("compressed_jsonl", models.CharField(max_length=300)),
|
||||
("embeddings_file", models.CharField(max_length=300)),
|
||||
(
|
||||
"config",
|
||||
models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to="database.configuration"),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="khojuser",
|
||||
name="uuid",
|
||||
field=models.UUIDField(verbose_name=models.UUIDField(default=uuid.uuid4, editable=False)),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="NotionConfig",
|
||||
fields=[
|
||||
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
|
||||
("token", models.CharField(max_length=200)),
|
||||
("compressed_jsonl", models.CharField(max_length=300)),
|
||||
("embeddings_file", models.CharField(max_length=300)),
|
||||
(
|
||||
"config",
|
||||
models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to="database.configuration"),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="GithubRepoConfig",
|
||||
fields=[
|
||||
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
|
||||
("name", models.CharField(max_length=200)),
|
||||
("owner", models.CharField(max_length=200)),
|
||||
("branch", models.CharField(max_length=200)),
|
||||
(
|
||||
"github_config",
|
||||
models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="database.githubconfig"),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="configuration",
|
||||
name="user",
|
||||
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
]
|
||||
0
src/database/migrations/__init__.py
Normal file
0
src/database/migrations/__init__.py
Normal file
53
src/database/models/__init__.py
Normal file
53
src/database/models/__init__.py
Normal file
@@ -0,0 +1,53 @@
|
||||
import uuid
|
||||
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
|
||||
|
||||
class KhojUser(AbstractUser):
|
||||
uuid = models.UUIDField(models.UUIDField(default=uuid.uuid4, editable=False))
|
||||
|
||||
|
||||
class GoogleUser(models.Model):
|
||||
user = models.OneToOneField(KhojUser, on_delete=models.CASCADE)
|
||||
sub = models.CharField(max_length=200)
|
||||
azp = models.CharField(max_length=200)
|
||||
email = models.CharField(max_length=200)
|
||||
name = models.CharField(max_length=200)
|
||||
given_name = models.CharField(max_length=200)
|
||||
family_name = models.CharField(max_length=200)
|
||||
picture = models.CharField(max_length=200)
|
||||
locale = models.CharField(max_length=200)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class Configuration(models.Model):
|
||||
user = models.OneToOneField(KhojUser, on_delete=models.CASCADE)
|
||||
|
||||
|
||||
class NotionConfig(models.Model):
|
||||
token = models.CharField(max_length=200)
|
||||
compressed_jsonl = models.CharField(max_length=300)
|
||||
embeddings_file = models.CharField(max_length=300)
|
||||
config = models.OneToOneField(Configuration, on_delete=models.CASCADE)
|
||||
|
||||
|
||||
class GithubConfig(models.Model):
|
||||
pat_token = models.CharField(max_length=200)
|
||||
compressed_jsonl = models.CharField(max_length=300)
|
||||
embeddings_file = models.CharField(max_length=300)
|
||||
config = models.OneToOneField(Configuration, on_delete=models.CASCADE)
|
||||
|
||||
|
||||
class GithubRepoConfig(models.Model):
|
||||
name = models.CharField(max_length=200)
|
||||
owner = models.CharField(max_length=200)
|
||||
branch = models.CharField(max_length=200)
|
||||
github_config = models.ForeignKey(GithubConfig, on_delete=models.CASCADE)
|
||||
|
||||
|
||||
class ConversationProcessorConfig(models.Model):
|
||||
conversation = models.JSONField()
|
||||
enable_offline_chat = models.BooleanField(default=False)
|
||||
Reference in New Issue
Block a user