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): def get_origin(self, headers):
""" Return origin of the received request""" """ Return origin of the received request"""
scheme = headers.get("X-Forwarded-Proto") if not headers:
if not scheme: return self.default_origin
scheme = "http"
scheme = headers.get("X-Forwarded-Proto")
host = headers.get("Host") host = headers.get("Host")
if not host: if not scheme or not host:
host = self.default_origin return self.default_origin
return scheme + "://" + host return scheme + "://" + host

View File

@ -9,10 +9,12 @@ import asyncio
from typing import Dict, Optional from typing import Dict, Optional
from pydantic import EmailStr, UUID4 from pydantic import EmailStr, UUID4
import passlib.pwd
from fastapi import Request, Response, HTTPException, Depends from fastapi import Request, Response, HTTPException, Depends
from fastapi_users import FastAPIUsers, models, BaseUserManager from fastapi_users import FastAPIUsers, models, BaseUserManager
from fastapi_users.manager import UserAlreadyExists
from fastapi_users.authentication import JWTAuthentication from fastapi_users.authentication import JWTAuthentication
from fastapi_users.db import MongoDBUserDatabase 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 # use custom model as model.BaseUserCreate includes is_* field
class UserCreate(models.CreateUpdateDictModel): class UserCreateIn(models.CreateUpdateDictModel):
""" """
User Creation Model User Creation Model exposed to API
""" """
email: EmailStr email: EmailStr
@ -52,6 +54,20 @@ class UserCreate(models.CreateUpdateDictModel):
newArchiveName: Optional[str] = "" 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): class UserUpdate(User, models.CreateUpdateDictModel):
""" """
@ -124,6 +140,27 @@ class UserManager(BaseUserManager[UserCreate, UserDB]):
) )
return await cursor.to_list(length=1000) 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( async def on_after_register_custom(
self, user: UserDB, user_create: UserCreate, request: Optional[Request] self, user: UserDB, user_create: UserCreate, request: Optional[Request]
): ):
@ -144,6 +181,8 @@ class UserManager(BaseUserManager[UserCreate, UserDB]):
user=user, user=user,
) )
is_verified = hasattr(user_create, "is_verified") and user_create.is_verified
if user_create.inviteToken: if user_create.inviteToken:
try: try:
await self.archive_ops.handle_new_user_invite( await self.archive_ops.handle_new_user_invite(
@ -152,10 +191,11 @@ class UserManager(BaseUserManager[UserCreate, UserDB]):
except HTTPException as exc: except HTTPException as exc:
print(exc) print(exc)
if not is_verified:
# if user has been invited, mark as verified immediately # if user has been invited, mark as verified immediately
await self._update(user, {"is_verified": True}) await self._update(user, {"is_verified": True})
else: elif not is_verified:
asyncio.create_task(self.request_verify(user, request)) asyncio.create_task(self.request_verify(user, request))
async def on_after_forgot_password( async def on_after_forgot_password(
@ -202,7 +242,7 @@ def init_users_api(app, user_manager):
lambda: user_manager, lambda: user_manager,
[jwt_authentication], [jwt_authentication],
User, User,
UserCreate, UserCreateIn,
UserUpdate, UserUpdate,
UserDB, UserDB,
) )
@ -261,4 +301,6 @@ def init_users_api(app, user_manager):
app.include_router(users_router, prefix="/users", tags=["users"]) app.include_router(users_router, prefix="/users", tags=["users"])
asyncio.create_task(user_manager.create_super_user())
return fastapi_users return fastapi_users

View File

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

View File

@ -5,6 +5,13 @@ name: browsertrix-cloud
registration_enabled: 1 registration_enabled: 1
jwt_token_lifetime_minutes: 60 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 # API Image
# ========================================= # =========================================

View File

@ -9,6 +9,11 @@ MONGO_INITDB_ROOT_PASSWORD=example
MINIO_ROOT_USER=ADMIN MINIO_ROOT_USER=ADMIN
MINIO_ROOT_PASSWORD=PASSW0RD 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_ENDPOINT_URL=http://minio:9000/test-bucket/
STORE_ACCESS_ENDPOINT_URL=http://localhost:9000/test-bucket/ STORE_ACCESS_ENDPOINT_URL=http://localhost:9000/test-bucket/
STORE_ACCESS_KEY=ADMIN STORE_ACCESS_KEY=ADMIN