Config superuser (#59)

* backend: automatically create super user, fixes #57
- if SUPERUSER_EMAIL is set, superuser is created with `is_superuser` and `is_verified` settings, if user doesn't already exist.
- if SUPERUSER_PASSWORD if set, the password for superuser is set, otherwise a random password is generated
update sample SUPERUSER_EMAIL and SUPERUSER_PASSWORD in config file and chart.
- ensure verification email is not sent if user already verified
This commit is contained in:
Ilya Kreymer 2021-12-05 14:12:42 -08:00 committed by GitHub
parent eaf8055063
commit 53beb84c01
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 69 additions and 12 deletions

View File

@ -34,13 +34,13 @@ class EmailSender:
def get_origin(self, headers):
""" Return origin of the received request"""
scheme = headers.get("X-Forwarded-Proto")
if not scheme:
scheme = "http"
if not headers:
return self.default_origin
scheme = headers.get("X-Forwarded-Proto")
host = headers.get("Host")
if not host:
host = self.default_origin
if not scheme or not host:
return self.default_origin
return scheme + "://" + host

View File

@ -9,10 +9,12 @@ import asyncio
from typing import Dict, Optional
from pydantic import EmailStr, UUID4
import passlib.pwd
from fastapi import Request, Response, HTTPException, Depends
from fastapi_users import FastAPIUsers, models, BaseUserManager
from fastapi_users.manager import UserAlreadyExists
from fastapi_users.authentication import JWTAuthentication
from fastapi_users.db import MongoDBUserDatabase
@ -35,10 +37,10 @@ class User(models.BaseUser):
# ============================================================================
# use custom model as model.BaseeserCreate includes is_* fields which should not be set
class UserCreate(models.CreateUpdateDictModel):
# use custom model as model.BaseUserCreate includes is_* field
class UserCreateIn(models.CreateUpdateDictModel):
"""
User Creation Model
User Creation Model exposed to API
"""
email: EmailStr
@ -52,6 +54,20 @@ class UserCreate(models.CreateUpdateDictModel):
newArchiveName: Optional[str] = ""
# ============================================================================
class UserCreate(models.BaseUserCreate):
"""
User Creation Model
"""
name: Optional[str] = ""
inviteToken: Optional[str]
newArchive: bool
newArchiveName: Optional[str] = ""
# ============================================================================
class UserUpdate(User, models.CreateUpdateDictModel):
"""
@ -124,6 +140,27 @@ class UserManager(BaseUserManager[UserCreate, UserDB]):
)
return await cursor.to_list(length=1000)
async def create_super_user(self):
""" Initialize a super user from env vars """
email = os.environ.get("SUPERUSER_EMAIL")
password = os.environ.get("SUPERUSER_PASSWORD")
if not email:
print("No superuser defined", flush=True)
return
if not password:
password = passlib.pwd.genword()
try:
res = await self.create(
UserCreate(email=email, password=password, is_superuser=True, newArchive=False, is_verified=True)
)
print(f"Super user {email} created", flush=True)
print(res, flush=True)
except UserAlreadyExists:
print(f"User {email} already exists", flush=True)
async def on_after_register_custom(
self, user: UserDB, user_create: UserCreate, request: Optional[Request]
):
@ -144,6 +181,8 @@ class UserManager(BaseUserManager[UserCreate, UserDB]):
user=user,
)
is_verified = hasattr(user_create, "is_verified") and user_create.is_verified
if user_create.inviteToken:
try:
await self.archive_ops.handle_new_user_invite(
@ -152,10 +191,11 @@ class UserManager(BaseUserManager[UserCreate, UserDB]):
except HTTPException as exc:
print(exc)
# if user has been invited, mark as verified immediately
await self._update(user, {"is_verified": True})
if not is_verified:
# if user has been invited, mark as verified immediately
await self._update(user, {"is_verified": True})
else:
elif not is_verified:
asyncio.create_task(self.request_verify(user, request))
async def on_after_forgot_password(
@ -202,7 +242,7 @@ def init_users_api(app, user_manager):
lambda: user_manager,
[jwt_authentication],
User,
UserCreate,
UserCreateIn,
UserUpdate,
UserDB,
)
@ -261,4 +301,6 @@ def init_users_api(app, user_manager):
app.include_router(users_router, prefix="/users", tags=["users"])
asyncio.create_task(user_manager.create_super_user())
return fastapi_users

View File

@ -23,6 +23,9 @@ stringData:
EMAIL_SENDER: "{{ .Values.email.sender_email }}"
EMAIL_PASSWORD: "{{ .Values.email.password }}"
SUPERUSER_EMAIL: "{{ .Values.superuser.email }}"
SUPERUSER_PASSWORD: "{{ .Values.superuser.password }}"
{{- range $storage := .Values.storages }}
---
apiVersion: v1

View File

@ -5,6 +5,13 @@ name: browsertrix-cloud
registration_enabled: 1
jwt_token_lifetime_minutes: 60
superuser:
# set this to enable a superuser admin
email: admin@example.com
# optional: if not set, automatically generated
password:
# API Image
# =========================================

View File

@ -9,6 +9,11 @@ MONGO_INITDB_ROOT_PASSWORD=example
MINIO_ROOT_USER=ADMIN
MINIO_ROOT_PASSWORD=PASSW0RD
SUPERUSER_EMAIL=admin@example.com
# if blank, a password is generated automatically
SUPERUSER_PASSWORD=
STORE_ENDPOINT_URL=http://minio:9000/test-bucket/
STORE_ACCESS_ENDPOINT_URL=http://localhost:9000/test-bucket/
STORE_ACCESS_KEY=ADMIN