Skip to content

Commit

Permalink
Implement initial admin user via env config
Browse files Browse the repository at this point in the history
closes #51
  • Loading branch information
bkis committed Jun 27, 2023
1 parent 85d216c commit 4298c80
Show file tree
Hide file tree
Showing 9 changed files with 106 additions and 25 deletions.
22 changes: 20 additions & 2 deletions .env.docker
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@

# === BASIC CONFIG ===

TEKST_WEB_PATH=/
TEKST_SERVER_URL=http://127.0.0.1:8087
TEKST_WEB_PATH=/
TEKST_API_PATH=/api

# TEKST_LOG_LEVEL=warning
# TEKST_USER_FILES_DIR=userfiles

# TEKST_CORS_ALLOW_ORIGINS=*
# TEKST_CORS_ALLOW_CREDENTIALS=true
# TEKST_CORS_ALLOW_METHODS=*
Expand All @@ -36,20 +38,36 @@ TEKST_API_PATH=/api
# TEKST_DEV_PORT=8000


# === EMAIL ===

# TEKST_EMAIL__SMTP_SERVER=
# TEKST_EMAIL__SMTP_PORT=25
# TEKST_EMAIL__SMTP_USER=
# TEKST_EMAIL__SMTP_PASSWORD=
# TEKST_EMAIL__SMTP_STARTTLS=true
# [email protected]


# === SECURITY ===

# TEKST_SECURITY__SECRET=change_this_to_something_random_and_secure
# TEKST_SECURITY__CLOSED_MODE=false
# TEKST_SECURITY__INIT_ADMIN_EMAIL=
# TEKST_SECURITY__INIT_ADMIN_PASSWORD=
# TEKST_SECURITY__USERS_ACTIVE_BY_DEFAULT=false

# TEKST_SECURITY__ENABLE_COOKIE_AUTH=true
# TEKST_SECURITY__ENABLE_JWT_AUTH=true
# TEKST_SECURITY__AUTH_COOKIE_NAME=tekstuserauth
# TEKST_SECURITY__AUTH_COOKIE_DOMAIN=
# TEKST_SECURITY__AUTH_COOKIE_LIFETIME=43200
# TEKST_SECURITY__ACCESS_TOKEN_LIFETIME=43200

# TEKST_SECURITY__ENABLE_JWT_AUTH=true
# TEKST_SECURITY__AUTH_JWT_LIFETIME=86400

# TEKST_SECURITY__RESET_PW_TOKEN_LIFETIME=3600
# TEKST_SECURITY__VERIFICATION_TOKEN_LIFETIME=3600

# TEKST_SECURITY__CSRF_COOKIE_NAME=XSRF-TOKEN
# TEKST_SECURITY__CSRF_HEADER_NAME=X-XSRF-TOKEN

Expand Down
12 changes: 10 additions & 2 deletions Tekst-API/.env
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@

# === BASIC CONFIG ===

# TEKST_WEB_PATH=/
# TEKST_SERVER_URL=http://127.0.0.1:8000
# TEKST_WEB_PATH=/
# TEKST_API_PATH=/api

# TEKST_LOG_LEVEL=warning
# TEKST_USER_FILES_DIR=userfiles

# TEKST_CORS_ALLOW_ORIGINS=*
# TEKST_CORS_ALLOW_CREDENTIALS=true
# TEKST_CORS_ALLOW_METHODS=*
Expand Down Expand Up @@ -50,16 +52,22 @@

# TEKST_SECURITY__SECRET=change_this_to_something_random_and_secure
# TEKST_SECURITY__CLOSED_MODE=false
# TEKST_SECURITY__INIT_ADMIN_EMAIL=
# TEKST_SECURITY__INIT_ADMIN_PASSWORD=
# TEKST_SECURITY__USERS_ACTIVE_BY_DEFAULT=false

# TEKST_SECURITY__ENABLE_COOKIE_AUTH=true
# TEKST_SECURITY__ENABLE_JWT_AUTH=true
# TEKST_SECURITY__AUTH_COOKIE_NAME=tekstuserauth
# TEKST_SECURITY__AUTH_COOKIE_DOMAIN=
# TEKST_SECURITY__AUTH_COOKIE_LIFETIME=43200
# TEKST_SECURITY__ACCESS_TOKEN_LIFETIME=43200

# TEKST_SECURITY__ENABLE_JWT_AUTH=true
# TEKST_SECURITY__AUTH_JWT_LIFETIME=86400

# TEKST_SECURITY__RESET_PW_TOKEN_LIFETIME=3600
# TEKST_SECURITY__VERIFICATION_TOKEN_LIFETIME=3600

# TEKST_SECURITY__CSRF_COOKIE_NAME=XSRF-TOKEN
# TEKST_SECURITY__CSRF_HEADER_NAME=X-XSRF-TOKEN

Expand Down
31 changes: 26 additions & 5 deletions Tekst-API/tekst/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -356,11 +356,32 @@ async def _create_user(user: UserCreate) -> UserRead:
log.warning("User already exists. Skipping.")


async def create_initial_superuser(user: UserCreate):
user.is_active = True
user.is_verified = True
user.is_superuser = True
await _create_user(user)
async def create_initial_superuser():
if _cfg.dev_mode:
return
if _cfg.security.init_admin_email and _cfg.security.init_admin_password:
if await User.find_one(User.email == _cfg.security.init_admin_email).exists():
log.warning("Initial admin account already exists. Skipping creation.")
return
log.info("Creating initial admin account...")
user = UserCreate(
email=_cfg.security.init_admin_email,
password=_cfg.security.init_admin_password,
username="admin",
first_name="Admin",
last_name="Admin",
affiliation="Admin",
)
user.is_active = True
user.is_verified = True
user.is_superuser = True
await _create_user(user)
log.warning(
"Created initial admin account. "
"PLEASE CHANGE THIS ACCOUNT'S EMAIL AND PASSWORD IMMEDIATELY!"
)
else:
log.warning("No initial admin account configured, skipping creation.")


async def create_sample_users():
Expand Down
23 changes: 15 additions & 8 deletions Tekst-API/tekst/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,16 +110,22 @@ class SecurityConfig(ModelBase):

secret: str = Field(default_factory=lambda: token_hex(32), min_length=16)
closed_mode: bool = False
init_admin_email: EmailStr | None = None
init_admin_password: str | None = None
users_active_by_default: bool = False

enable_cookie_auth: bool = True
enable_jwt_auth: bool = True
auth_cookie_name: str = "tekstuserauth"
auth_cookie_domain: str | None = None
auth_cookie_lifetime: conint(ge=3600) = 43200
access_token_lifetime: conint(ge=3600) = 43200

enable_jwt_auth: bool = True
auth_jwt_lifetime: int = conint(ge=3600)

reset_pw_token_lifetime: conint(ge=600) = 3600
verification_token_lifetime: conint(ge=600) = 3600

csrf_cookie_name: str = "XSRF-TOKEN"
csrf_header_name: str = "X-XSRF-TOKEN"

Expand All @@ -139,23 +145,24 @@ class TekstConfig(BaseSettings):
"""Platform config model"""

# basic
dev_mode: bool = False
server_url: HttpUrl = "http://127.0.0.1:8000"
web_path: str = "/"
api_path: str = "/api"
server_url: HttpUrl = "http://127.0.0.1:8000"
user_files_dir: str = "userfiles"
log_level: str = "warning"

# uvicorn asgi binding
dev_host: str = "127.0.0.1"
dev_port: int = 8000
log_level: str = "warning"
user_files_dir: str = "userfiles"

# CORS
cors_allow_origins: str | list[str] = ["*"]
cors_allow_credentials: bool = True
cors_allow_methods: str | list[str] = ["*"]
cors_allow_headers: str | list[str] = ["*"]

# development
dev_mode: bool = False
dev_host: str = "127.0.0.1"
dev_port: int = 8000

# special domain sub configs
email: EMailConfig = EMailConfig() # Email-related config
security: SecurityConfig = SecurityConfig() # security-related config
Expand Down
6 changes: 3 additions & 3 deletions Tekst-API/tekst/email/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def _send_email(*, to: str, subject: str, txt: str, html: str):
f"{_cfg.email.smtp_server}:{_cfg.email.smtp_port} "
f"(StartTLS: {_cfg.email.smtp_starttls})"
)
raise e
log.error(e)


def send_email(
Expand All @@ -99,8 +99,8 @@ def send_email(
templates[key]
.format(
web_url=urljoin(_cfg.server_url, _cfg.web_path).strip("/"),
**_cfg.info.dict(by_alias=False),
**to_user.dict(by_alias=False),
**_cfg.info.dict(by_alias=False, exclude_unset=False),
**to_user.dict(by_alias=False, exclude_unset=False),
**kwargs,
)
.strip()
Expand Down
2 changes: 1 addition & 1 deletion Tekst-API/tekst/routers/platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ async def get_platform_data(
"""Returns data the client needs to initialize"""
return PlatformData(
texts=await get_all_texts(ou),
settings=await PlatformSettingsDocument.find_one({}),
settings=await PlatformSettingsDocument.find_one(),
layer_types=layer_type_manager.get_layer_types_info(),
)

Expand Down
15 changes: 11 additions & 4 deletions Tekst-API/tekst/setup.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from tekst.auth import create_sample_users
from tekst.auth import create_initial_superuser, create_sample_users
from tekst.config import TekstConfig
from tekst.db import init_odm
from tekst.dependencies import get_db, get_db_client
Expand All @@ -12,13 +12,20 @@ async def app_setup(cfg: TekstConfig):
setup_logging()
log.info("Running Tekst pre-launch app setup...")

log.info("Checking SMTP config...")
if not cfg.email.smtp_server:
log.critical("No SMTP server configured. Aborting setup...")
exit(1)

init_layer_type_manager()
await init_odm(get_db(get_db_client(cfg), cfg))
await create_sample_users()
await create_sample_texts()

log.info("Creating initial platform settings from defaults...")
if not (await PlatformSettingsDocument.find_one({}).exists()):
if not (await PlatformSettingsDocument.find_one().exists()):
await PlatformSettingsDocument().create()
else:
log.warning("Platform settings already exist. Skipping settings creation.")

await create_sample_users()
await create_sample_texts()
await create_initial_superuser()
19 changes: 19 additions & 0 deletions docs/content/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ cp .env.docker .env

See [Configuration](#configuration) for details on initially configuring Tekst via this `.env` file.

!!! danger "Attention!"

You need one initial administrator account to manage your Tekst platform.
Please read [Initial Admin Account](#initial-admin-account) to learn how to set it up!

Build the docker images for the **Tekst-API** (server) and **Tekst-Web** (client) applications. **Important:** Whenever you decide to change one of `TEKST_WEB_PATH`, `TEKST_SERVER_URL` or `TEKST_API_PATH` in your `.env` file, you'll have to build the image for **Tekst-Web** (client) again, as these values are statically replaced in the code during the build process!

```sh
Expand Down Expand Up @@ -70,3 +75,17 @@ Read [this](https://docs.docker.com/engine/reference/commandline/compose/) to le

#### Instructions
> 🏗 TODO

## Initial Admin Account

To configure an initial admin account, follow these steps:

1. In your `.env` file, set `TEKST_SECURITY__INIT_ADMIN_EMAIL` (admin account initial email address) and `TEKST_SECURITY__INIT_ADMIN_PASSWORD` (admin account initial password). The Password **must** have at least 8 characters and **must** container at least one of each:
- lowercase letters
- UPPERCASE LETTERS
- digits 0-9
2. Follow the setup instructions depending on the deployment strategy you chose above.
3. When setup is finished and everything is working, **immediately log in with your initial administrator account and change its password!**

Alternatively, and this is for advanced users, you can leave the two values blank in `.env` and just register a new user via the Webclient as normal. You then have to log into the database modify the documents for this initial account to be activated, verified and a superuser (admin).
1 change: 1 addition & 0 deletions docs/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ nav:
- 'setup.md'

markdown_extensions:
- admonition
- pymdownx.highlight:
anchor_linenums: true
line_spans: __span
Expand Down

0 comments on commit 4298c80

Please sign in to comment.