Go to file
Johan Carlberg f574764867 Allow database connection pool configuration. 2026-02-22 12:43:16 +01:00
.vscode Public version. 2026-01-18 20:35:36 +01:00
Public When loading lists on web pages, allow items to have additional loaders that runs afterwards. 2026-02-22 10:22:01 +01:00
Resources Different life time of user tokens for invites and forgotten passwords. 2026-02-20 21:45:30 +01:00
Sources Allow database connection pool configuration. 2026-02-22 12:43:16 +01:00
Tests/SampleAppTests Different life time of user tokens for invites and forgotten passwords. 2026-02-20 21:45:30 +01:00
.dockerignore Public version. 2026-01-18 20:35:36 +01:00
.gitignore Public version. 2026-01-18 20:35:36 +01:00
Dockerfile Public version. 2026-01-18 20:35:36 +01:00
Package.resolved Spelling. 2026-01-24 13:02:22 +01:00
Package.swift Spelling. 2026-01-24 13:02:22 +01:00
README.md Spelling. 2026-01-24 13:02:22 +01:00

README.md

ManageableUsers

A package providing basic user management when using the Vapor web framework and a PostgreSQL database.

Provides controllers and middleware for authenticating users. This includes web pages for logging in, handling of forgotten password by sending tokens via email, and logging out.

Provides controllers for managing users. This includes administration pages and inviting users via email.

Note: This package uses raw SQL for accessing the database, so there are no Fluent model for users, but the database is connected using Fluent, so the it can still be used for other items.

Try out

The package contains an executable target SampleApp with a minimal implementation demonstrating the functionality.

You can try this to asses the functionality provided by this package, or you can use it as a template for starting your own project.

Database

To run SampleApp, a database is needed.

If you don't already have PostgreSQL installed, refer to postgresql.org for options.

There's a SQL script provided for creating the tables needed by SampleApp.

$ psql --file=Resources/Database/Create.sql

For creating a new database named sampleapp, create the tables, and a corresponding user do the following:

$ psql --dbname=postgres --command="CREATE DATABASE sampleapp"
$ psql --dbname=sampleapp --command="CREATE ROLE sampleapp WITH LOGIN PASSWORD 'sampleapp_password'"
$ psql --dbname=sampleapp --file=Resources/Database/Create.sql
$ psql --dbname=sampleapp --command="GRANT CONNECT ON DATABASE sampleapp TO sampleapp"
$ psql --dbname=sampleapp --command="GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO sampleapp"

There's also a script for creating an initial user:

$ psql --dbname=sampleapp --file=Resources/Database/InitialUser.sql --variable=email=someone@example.com

The script will return a password URL for setting the password (assuming that the service runs on https://example.com/, it needs to be changed to the actual host to be usable). The email address is used for logging in, and can also be used for resetting passwords via email if you have email delivery setup.

Running Application

You can run the application with swift run, or from an IDE such as Xcode.

A number of environment variables are needed for proper function.

  • DATABASE_HOST: Host for the PostgreSQL server, defaults to localhost
  • DATABASE_PORT: Port for the PostgreSQL server, defaults to 5432
  • DATABASE_NAME: Name of the database, defaults to sampleapp
  • DATABASE_USERNAME: Username for accessing the database, defaults to sampleapp
  • DATABASE_PASSWORD: Password for accessing the database, defaults to sampleapp_password
  • BASE_URL: URL for accessing the web site (e.g. http://localhost:8080)
  • SMTP_HOST, SMTP_PORT, SMTP_ENCRYPTION, SMTP_TIMEOUT, SMTP_USERNAME, SMTP_PASSWORD and SMTP_USE_ESMTP: Configuring email delivery, see the documentation for the SwiftSMTP package for details.
  • LOG_LEVEL: Desired level of logging, defaulting to info.

The application will write to the log that it has started and which port it's listening on.

Using application

Use the URL returned from the InitialUser.sql script above in a browser. Make sure to adapt it to the actual setup (e.g. http://localhost:8080/auth/password/2lG91bumxxvm9Ky7xLy1Nxz2mJoxjRCS with defaults and without a reverse proxy).

A form for setting a password should be provided at that URL. After setting a password, that password and the email address provided to the InitialUser.sql script can then be used for logging in.

Getting Started

Create a new Vapor project, or reuse an existing one.

Dependencies

Add dependency on manageable-users by adding the package to Package.swift as

        .package(url: "https://git.carlberg.org/public/manageable-users.git", from: "1.0.0"),

…and adding to the executable target's dependencies as

                .product(name: "ManageableUsers", package: "manageable-users"),
``

Copy the contents of the `Resources/Views` folder from the `manageable-users` into the `Resources/Views` folder
in the new project, and also copy the contents of the `Public` folder into the `Public` folder.

In the `configure.swift` file, add the import
```swift
import ManageableUsers

…and add

    try await ManageableUsers.configure (app)

into the configure() function.

In the routes.swift file, add the import

import ManageableUsers

…and replace everything in the routes() function with

    let sessioned = app.grouped (app.sessions.middleware)
        .grouped (BasicUser.sessionAuthenticator())
    let loggedIn = sessioned
        .grouped (BasicUser.redirectMiddleware (path: "auth/login"))
    let api = sessioned
        .grouped("api")

    AuthenticationController<BasicUser>().routes (sessioned, api: api)
    AdminController<BasicUser>().routes (loggedIn, api: api)

    struct WelcomeContext: Encodable {
        let user: BasicUser?
        let section: String
    }

    loggedIn.get { req async throws in
        return try await req.view.render("welcome", WelcomeContext (user: req.auth.get (BasicUser.self), section: "home"))
    }

Extending

To add useful functionality, just add more routes to routes.swift.

To use a consistent look, use base.leaf in your Leaf templates for pages.

To add items to the main menu, do that in configure.swift by replacing

    try await ManageableUsers.configure (app)

with something like

    try await ManageableUsers.configure(app, mainMenu: [MenuItem (section: "inventory", path: "inventory", name: "Inventory")])

For MenuItem

  • section corresponds to the section included in the rendering context supplied to Leaf.
  • path corresponds to the URL path to the start page of that section.
  • name is the name that will be displayed in the menu.
  • role is an optional role that is required to access that section.
    • If nil or omitted, it will be shown to all logged in users.
    • Corresponds to the name column in the roles table.