Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature addition: Multi-Factor Authentication #401

Draft
wants to merge 22 commits into
base: develop
Choose a base branch
from
Draft

Conversation

chesspro13
Copy link

@chesspro13 chesspro13 commented Sep 7, 2024

Features added

TOTP (Time-based One-Time Password) with recovery codes
OpenID/SSO via Google (for now)

Documentation

Testing Instructions

TOTP

  1. Start Trilium Notes normally.
  2. Go to "Menu" -> "Options" -> "MFA"
  3. Click the "Generate TOTP Secret" button
  4. Copy the generated secret to your authentication app/extension
  5. Set an environment variable "TOTP_SECRET" as the generated secret. Environment variables can be set with a .env file in the root directory, by defining them in the command line, or with a docker container.
    # .env in the project root directory
    TOTP_ENABLED="true"
    TOTP_SECRET="secret"
    # Terminal/CLI
    export TOTP_ENABLED="true"
    export TOTP_SECRET="secret"
    # Docker
    docker run -p 8080:8080 -v ~/trilium-data:/home/node/trilium-data -e TOTP_ENABLED="true" -e TOTP_SECRET="secret" triliumnext/notes:[VERSION]
  6. Restart Trilium
  7. Go to "Options" -> "MFA"
  8. Click the "Generate Recovery Codes" button
  9. Save the recovery codes. Recovery codes can be used once in place of the TOTP if you loose access to your authenticator. After a rerecovery code is used, it will show the unix timestamp when it was used in the MFA options tab.
  10. Load the secret into an authentication app like google authenticator

OpenID

Currently only compatible with Google. Other services like Authentik and Auth0 are planned on being added.

In order to setup OpenID, you will need to setup a authentication provider. This requires a bit of extra setup. Follow these instructions to setup an OpenID service through google.

Set an environment variable "SSO_ENABLED" to true and add the client ID and secret you obtained from google. Environment variables can be set with a .env file in the root directory, by defining them in the command line, or with a docker container.

.env File

# .env in the project root directory
SSO_ENABLED="true"
BASE_URL="http://localhost:8080"
CLIENT_ID=<client ID from google>
SECRET=<client secret from google>

Environment variable (linux)

export SSO_ENABLED="true"
export BASE_URL="http://localhost:8080"
export CLIENT_ID=<client ID from google>
export SECRET=<client secret from google>

Docker

docker run -d -p 8080:8080 -v ~/trilium-data:/home/node/trilium-data -e SSO_ENABLED="true" -e BASE_URL="http://localhost:8080" -e CLIENT_ID=<client ID from google> -e SECRET=<client secret from google> triliumnext/notes:[VERSION]

After you restart Trilium Notes, you will be redirected to Google's account selection page. Login to an account and Trilium Next will bind to that account, allowing you to login with it.

You can now login using your google account.

@chesspro13 chesspro13 linked an issue Sep 7, 2024 that may be closed by this pull request
@chesspro13 chesspro13 marked this pull request as ready for review September 7, 2024 22:17
@chesspro13 chesspro13 requested review from adoriandoran and a team September 7, 2024 22:18
@chesspro13 chesspro13 added the enhancement New feature or request label Sep 7, 2024
@perfectra1n
Copy link

This is super cool, thanks for doing this.

Is it also possible to configure the .env variables via the local environment variables? I was poking around in the commits, and didn't see any documentation modified for this change, but I'm assuming that you're saving those changes for once someone else with a much bigger brain than myself reviews it! :)

I'll also review the additional routes for the OTP.

@chesspro13
Copy link
Author

chesspro13 commented Sep 7, 2024

@perfectra1n the environment variables can be set with environment variables (ie export TOTP_ENABLED="true"), -e with docker, and in a .env file in the root directory.

Honestly I forgot to update documentation. Whoops!

edit: I'm working on adding some pages now.

@chesspro13
Copy link
Author

chesspro13 commented Sep 9, 2024

@perfectra1n

Docs complete here.

@chesspro13 chesspro13 requested review from eliandoran and removed request for adoriandoran September 10, 2024 19:21
@chesspro13 chesspro13 changed the title Feature addition: Updated MFA Feature addition: Multi-Factor Authentication Sep 12, 2024
Copy link

@JYC333 JYC333 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your great work! I didn't finish testing the functionality, but the setup steps need some changes I believe.

Since you are adding a new table, and it doesn't add it automatically. I got an error here:

image

I'm not sure whether I'm doing something wrong, if not, I think this PR also need to fix the database migration stuff.

I don't know exactly how the database version is handled in Trilium, but for syncing, the database version should be the same. So after we merge this PR, it won't compatible with the latest Trilium from Zadam. (Correct me if I'm wrong) It could be the first big step for TriliumNext, not sure how careful we should be hhh.

Some minor stuffs I find so far:

  1. MFA in option doesn't in the "right" place if I already have a database (I mean not start from scratch).
    image
  2. We have already upgrade bootstrap from v4 to v5, so some layouts are changed. Not sure whether you would like to fix it in this PR, it's ok to do it in a seperate PR I guess.

@chesspro13 chesspro13 marked this pull request as draft September 13, 2024 16:15
@chesspro13
Copy link
Author

@JYC333 I'll take any feedback I can get!

Since you are adding a new table, and it doesn't add it automatically. I got an error here:

I do have a way to create the table if it doesn't exist, however it looks like it's in the wrong place since you got that error. I'll add it to the startup sequence.

I don't know exactly how the database version is handled in Trilium, but for syncing, the database version should be the same. So after we merge this PR, it won't compatible with the latest Trilium from Zadam. (Correct me if I'm wrong) It could be the first big step for TriliumNext, not sure how careful we should be hhh.

Do you mean the versioning change from BetterSqlite3? That was changed earlier with the dependency cleanup.

1. MFA in option doesn't in the "right" place if I already have a database (I mean not start from scratch).
2. We have already upgrade bootstrap from v4 to v5, so some layouts are changed. Not sure whether you would like to fix it in this PR, it's ok to do it in a seperate PR I guess.

I will look into it, but honestly my UI/UX skills are nothing to write home about.

@JYC333
Copy link

JYC333 commented Sep 13, 2024

Do you mean the versioning change from BetterSqlite3? That was changed earlier with the dependency cleanup.

No, I mean the database version for the syncing, I think the database version should be the same bewteen client and server. So the previous Trilium (like 0.63.7) won't be compatible. But I see sync version here also, so I'm not sure whether I'm right.

image

I will look into it, but honestly my UI/UX skills are nothing to write home about.

You can also leave this for later if you don't want spend too much time on this, I can help with this.

@perfectra1n
Copy link

I'm also not sure if it's just me, but running the branch on a brand new instance brings the following errors when navigating to the setup page:

{"message":"/usr/src/app/src/views/setup.ejs:6\n    4|     <meta charset=\"utf-8\">\n    5|     <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1\">\n >> 6|     <title><%= t(\"setup.title\") %></title>\n    7| \n    8|     <style>\n    9|         .lds-ring {\n\nt is not defined"}

Full output of the Docker run command:

[root on DESKTOP-M0FBO5A] ~
 »  docker run -v /root/triliumtest:/home/node/trilium-data -p 8080:8080 gitea.example.com/perf3ct/testnotes:mfatest
No USER_UID specified, leaving 1000
No USER_GID specified, leaving 1000
(node:9) ExperimentalWarning: Importing JSON modules is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
Generated session secret
DB not initialized, please visit setup page - http://[your-server-host]:8080 to see instructions on how to initialize Trilium.
DB size: 4 KB
(node:9) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
{
  "appVersion": "0.90.6-beta",
  "dbVersion": 228,
  "nodeVersion": "v22.8.0",
  "syncVersion": 32,
  "buildDate": "2024-09-07T18:36:34Z",
  "buildRevision": "7c0d6930fa8f20d269dcfbcbc8f636a25f6bb9a7",
  "dataDirectory": "/home/node/trilium-data",
  "clipperProtocolVersion": "1.0",
  "utcDateTime": "2024-09-13T18:23:05.503Z"
}
CPU model: AMD Ryzen 7 5800X 8-Core Processor, logical cores: 16, freq: 0 Mhz
Trusted reverse proxy: false
App HTTP server starting up at port 8080
Listening on port 8080
200 GET /api/health-check with 15 bytes took 3ms
ReferenceError: /usr/src/app/src/views/setup.ejs:6
    4|     <meta charset="utf-8">
    5|     <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
 >> 6|     <title><%= t("setup.title") %></title>
    7|
    8|     <style>
    9|         .lds-ring {

t is not defined
    at eval ("/usr/src/app/src/views/setup.ejs":12:7)
    at setup (/usr/src/app/node_modules/ejs/lib/ejs.js:703:17)
    at tryHandleCache (/usr/src/app/node_modules/ejs/lib/ejs.js:274:36)
    at exports.renderFile [as engine] (/usr/src/app/node_modules/ejs/lib/ejs.js:491:10)
    at View.render (/usr/src/app/node_modules/express/lib/view.js:135:8)
    at tryRender (/usr/src/app/node_modules/express/lib/application.js:657:10)
    at Function.render (/usr/src/app/node_modules/express/lib/application.js:609:3)
    at ServerResponse.render (/usr/src/app/node_modules/express/lib/response.js:1048:7)
    at setupPage (file:///usr/src/app/src/routes/setup.js:24:9)
    at Function.cb (file:///usr/src/app/src/routes/routes.js:377:34) {
  path: '/usr/src/app/src/views/setup.ejs'
}
200 GET /api/health-check with 15 bytes took 0ms
200 GET /api/health-check with 15 bytes took 1ms

I checked out the feature/MFA branch and then built the Docker container on it 👀

@perfectra1n
Copy link

Oh, looks like it might be a fatfinger somewhere with the t in setup.ejs lol

@perfectra1n
Copy link

Yeah I'm not sure why it can't find what t is supposed to be, here, does it work for you when you create the Docker container @chesspro13 ?

@chesspro13
Copy link
Author

chesspro13 commented Sep 14, 2024

Since you are adding a new table, and it doesn't add it automatically. I got an error here:

@JYC333 I was able to reproduce this and fix it.

No, I mean the database version for the syncing, I think the database version should be the same bewteen client and server. So the previous Trilium (like 0.63.7) won't be compatible. But I see sync version here also, so I'm not sure whether I'm right.

I'll have to look into that. I'm not familiar with the client version.

@perfectra1n When I build docker it is successful, however the setup page doesn't render any text when I tried it again.
Screenshot from 2024-09-14 10-31-18
It's only behaving this was for the Docker image.

Both mobile.ejs and set_passowrd.ejs have some too, but there are none in login.ejs. The only one I made modifications to is login.ejs.

@chesspro13
Copy link
Author

chesspro13 commented Sep 14, 2024

@perfectra1n it looks like the t is part of the i18next package for translations.

const t: TFunction<["translation", ...string[]], undefined>

@perfectra1n
Copy link

Hmm, I wonder why it's throwing an error then, @eliandoran do you have any idea why by chance?

@chesspro13
Copy link
Author

chesspro13 commented Sep 14, 2024

@perfectra1n I can confirm that i18next is what is causing the text to not render. I'm unable to replicate your error. Have you updated all packages with npm i after switching to the new branch?

@perfectra1n
Copy link

perfectra1n commented Sep 14, 2024

image

I can confirm that I did not, but I believe those commands are built within the Docker container without cache -- so you can't replicate the issue when using the Docker container?

@chesspro13
Copy link
Author

chesspro13 commented Sep 14, 2024

so you can't replicate the issue when using the Docker container?

@JYC333 I can't replicate your issue. When I build the docker image it runs, but when I navigate to localhost:8080/setup no text is rendered (the picture I posted above). I think we are both having issues with the same package, but I'm still confused why we are getting different results since we are using docker. I may be wildly wrong, but I'm guessing i18next needs to be installed prior to running the docker build script because the typescript gets compiled in build-docker.sh and copied to the container afterwards in the Dockerfile. My guess is it throws off the translations. ¯\(ツ)

@JYC333
Copy link

JYC333 commented Sep 15, 2024

so you can't replicate the issue when using the Docker container?

@JYC333 I can't replicate your issue. When I build the docker image it runs, but when I navigate to localhost:8080/setup no text is rendered (the picture I posted above). I think we are both having issues with the same package, but I'm still confused why we are getting different results since we are using docker. I may be wildly wrong, but I'm guessing i18next needs to be installed prior to running the docker build script because the typescript gets compiled in build-docker.sh and copied to the container afterwards in the Dockerfile. My guess is it throws off the translations. ¯_(ツ)_/¯

Sorry I didn't follow here, is the issue here you for the database issue?

And I didn't try to start with docker, I always run with npm run start-server for development, not sure what's the behaviors when using docker.

@eliandoran
Copy link
Contributor

@perfectra1n I can confirm that i18next is what is causing the text to not render. I'm unable to replicate your error. Have you updated all packages with npm i after switching to the new branch?

There are still some bugs related to internationalization of the server that I'm trying to fix. If it occurs on 'develop' as well it can be ignored for this particular PR.

@perfectra1n
Copy link

@chesspro13 is this still good for testing? :)

I wonder what commits I would have to cherry pick to get the Docker container to build, or how would be best to test it...what have you been doing thus far to test it on your branch?

@chesspro13
Copy link
Author

chesspro13 commented Sep 29, 2024

so you can't replicate the issue when using the Docker container?

@JYC333 I can't replicate your issue. When I build the docker image it runs, but when I navigate to localhost:8080/setup no text is rendered (the picture I posted above). I think we are both having issues with the same package, but I'm still confused why we are getting different results since we are using docker. I may be wildly wrong, but I'm guessing i18next needs to be installed prior to running the docker build script because the typescript gets compiled in build-docker.sh and copied to the container afterwards in the Dockerfile. My guess is it throws off the translations. ¯_(ツ)_/¯

@perfectra1n I tried it in my dev environment as well as on a fresh VM today. The only issue I have is the one with i18next and text not showing up in the setup screen. What OS are you using?

@JYC333 I got the conversations mixed up. That last message I tagged you in was probably supposed to tag @perfectra1n.

Been busy with work related projects and school.

auth0Logout: false,
baseURL: process.env.BASE_URL,
clientID: process.env.CLIENT_ID,
issuerBaseURL: "https://accounts.google.com/.well-known/openid-configuration",
Copy link

@minecraftchest1 minecraftchest1 Oct 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make issuerBaseUrl load process.env.OPENID_CONFIG_URL', and now you have support for more services for OpenID Connect. would also prefix all of the Openid Connect related variables with something like OIDC_' or similar to prevent conflicts.

@@ -69,6 +69,7 @@
"dayjs": "^1.11.13",
"dayjs-plugin-utc": "0.1.2",
"debounce": "^2.1.0",
"dotenv": "^16.4.5",
Copy link

@JeffrinCh JeffrinCh Nov 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

chesspro13 Do we need dotenv now? we can use the inbuilt functionality of nodejs now https://nodejs.org/en/learn/command-line/how-to-read-environment-variables-from-nodejs

@eliandoran
Copy link
Contributor

Hi, @chesspro13 .

What's the status of this PR, do you have the time to work on it?

@pano9000
Copy link
Contributor

quick comment on the env naming:

I personally feel like the env vars should be prefixed with some sort of code, to make sure they are clearly belonging to TriliumNext - which will reduce the chance of accidentally resetting/overwriting these.

E.g. instead of TOTP_ENABLED maybe it could be TRILIUMNEXT_TOTP_ENABLED or just TRILIUM_TOTP_ENABLED

What do you think?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

(Feature request) Multi-factor authentication
7 participants