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

Document + example how to implement a custom login #145

Closed
almarklein opened this issue Feb 3, 2022 · 12 comments · Fixed by #219
Closed

Document + example how to implement a custom login #145

almarklein opened this issue Feb 3, 2022 · 12 comments · Fixed by #219
Labels
documentation Improvements or additions to documentation

Comments

@almarklein
Copy link
Owner

For self-hosters.

@almarklein almarklein added the documentation Improvements or additions to documentation label Feb 3, 2022
@boehs
Copy link

boehs commented Feb 13, 2022

I have done on my fork: https://github.com/boehs/timetagger

@almarklein
Copy link
Owner Author

@boehs I don't see where. Was it on a branch that has been deleted or something?

@boehs
Copy link

boehs commented Feb 22, 2022

@boehs I don't see where. Was it on a branch that has been deleted or something?

Where did it go!? I'm so confused, I deployed it to my server by git cloning.... In any case, it should be there now

@sergeolkhovik
Copy link

Gentlemen, I want to run an instance of TT under nginx + docker environment and have some auth for only me. Let me share this info, hope it will be useful.
So I did this in nginx.conf:

    location /timetagger {
        proxy_pass http://timetagger:80;
    }
    location /timetagger/login {
        auth_basic "Timetagger";
        auth_basic_user_file /etc/nginx/conf.d/auth;
        proxy_pass http://timetagger:80;
    }

Auth file is created with a htpasswd tool and contains only one user.
And here's my docker-compose conf:

  timetagger:
    container_name: timetagger
    build:
      context: timetagger
      dockerfile: Dockerfile
    environment:
      - TIMETAGGER_DATADIR=/root/timedata
    volumes:
      - ./conf/timetagger:/root
      - ./timetagger/timetagger:/root/timetagger
    ports:
      - 8081:80
    networks:
      - backend

Next, I copied run.py from timetagger cloned repo into conf/timetagger and did these modifications:

% diff -u conf/timetagger/run.py timetagger/run.py
--- conf/timetagger/run.py      2022-02-22 12:34:53.927671913 +0200
+++ timetagger/run.py   2022-02-21 12:31:29.114317818 +0200
@@ -20,10 +20,6 @@
 )


-AUTH_KEY='authorization'
-AUTH_PW='Basic xxxx'
-AUTH_USER='xxxx'
-
 logger = logging.getLogger("asgineer")

 # Get sets of assets provided by TimeTagger
@@ -103,13 +99,11 @@
     """

     # Establish that we can trust the client
-    #return 403, {}, repr(request.headers)
-    #if not (request.host in ("localhost", "127.0.0.1")):
-    if not (request.host in ("localhost", "127.0.0.1") or AUTH_KEY in request.headers and request.headers[AUTH_KEY] == AUTH_PW):
+    if request.host not in ("localhost", "127.0.0.1"):
         return 403, {}, "forbidden: must be on localhost"

     # Return the webtoken for the default user
-    token = await get_webtoken_unsafe(AUTH_USER)
+    token = await get_webtoken_unsafe("defaultuser")
     return 200, {}, dict(token=token)

So nginx passes authorization header that will contain 'Basic xxxx', and that value may be parsed (this is base64-value that has user:password text), but I just saved that entire header value and check it on request.
I didn't check cli version yet, but I hope all will be fine, I see API token generated.

@atomicangel
Copy link

@boehs I don't see where. Was it on a branch that has been deleted or something?

Where did it go!? I'm so confused, I deployed it to my server by git cloning.... In any case, it should be there now

I don't see anything on your repo that is different from what is on this repo about making a custom login.

Also in regards to the above post, I'm not sure what needs to be put into the modified run.py file. My presumption is that anything that was a - line is in the modified file, and the + is what is in the original.

@atomicangel
Copy link

atomicangel commented May 6, 2022

EDIT: I have been successful in getting it to authenticate against Authelia. The login process is now automatic. Here's what I added to the run.py file under webtoken_for_localhost:


    AUTH_USER = request.headers['remote-user']
    token = await get_webtoken_unsafe(AUTH_USER)

I also added this to the api_handler section and it properly deauths the user, and in the event they authenticated against Authelia first for a different domain it will show "Unauthorized" with the login button to take care of that scenario as well.

AUTH_USER = request.headers['remote-user']
        CURRENT_USER = auth_info['username']
        if AUTH_USER != CURRENT_USER:
            return 401, {}, f"Unauthorized"

I also changed my nginx config for the site under sites-available (I'm running nginx) to do the following:

auth_request_set $target_url https://timetaggerurl.example.com/timetagger/login;

This will force Authelia to always redirect to the login page of the Timetagger instance, thus ensuring that a direct re-auth with Authelia will immediately reset the web-token regardless of the previously logged in user. This is makes it a seamless login process to the end user without much modification of the Timetagger application itself.

@atomicangel
Copy link

Update: The above method breaks the API configuration, here is the error I am seeing when trying to use the API:

[ERROR 2022-05-10 16:09:11] KeyError in request handler: 'remote-user'
Traceback (most recent call last):
  File "/usr/local/lib/python3.8/dist-packages/asgineer/_app.py", line 141, in _handle_http
    result = await handler(request)
  File "./run.py", line 59, in main_handler
    return await api_handler(request, path)
  File "./run.py", line 86, in api_handler
    AUTH_USER = request.headers['remote-user']
KeyError: 'remote-user'

If I remove the following:

AUTH_USER = request.headers['remote-user']
        CURRENT_USER = auth_info['username']
        if AUTH_USER != CURRENT_USER:
            return 401, {}, f"Unauthorized"

The user is still properly de-auth'd but it doesn't present a login button, however the API then works as expected. So for now, I'm going to leave that bit commented out so it will work and if you have any insight as to what needs corrected I'm wiling to try it.

@almarklein
Copy link
Owner Author

The keyerror means that the 'remote-user' field is not present in the header. You could replace that line with

    AUTH_USER = request.headers.get('remote-user', None)

and maybe test if AUTH_USER is None depending on what you want to do.

@dorianim
Copy link
Contributor

dorianim commented May 29, 2022

What I did is this:

async def api_handler(request, path):
    # Some endpoints do not require authentication
    if not path and request.method == "GET":
        return 200, {}, "See https://timetagger.readthedocs.io"
    elif path == "webtoken_for_localhost":
        return await webtoken_for_localhost(request)

    # Authenticate and get user db
    try:
        auth_info, db = await authenticate(request)
        if 'x-authentik-username' in request.headers and auth_info["username"] != request.headers['x-authentik-username']:
            raise AuthException("User changed")

    except AuthException as err:
        return 401, {}, f"Please login again: {err}"

    # Handle endpoints that require authentication
    return await api_handler_triage(request, path, auth_info, db)


async def webtoken_for_localhost(request):
    # Establish that we can trust the client
    if 'x-authentik-username' not in request.headers:
        return 403, {}, "forbidden: must be on authenticated"
    remoteUser = request.headers['x-authentik-username']
    if not remoteUser:
        return 403, {}, "forbidden: must be on authenticated"

    # Return the webtoken for the default user
    token = await get_webtoken_unsafe(remoteUser)
    return 200, {}, dict(token=token)

It also makes sure, that the authenticated user is still the logged-in user.

@boehs
Copy link

boehs commented May 30, 2022

@boehs I don't see where. Was it on a branch that has been deleted or something?

Where did it go!? I'm so confused, I deployed it to my server by git cloning.... In any case, it should be there now

I don't see anything on your repo that is different from what is on this repo about making a custom login.

Also in regards to the above post, I'm not sure what needs to be put into the modified run.py file. My presumption is that anything that was a - line is in the modified file, and the + is what is in the original.

No clue what the heck is going on, you can see the diff here main...boehs:main

@almarklein
Copy link
Owner Author

Timatagger now has basic authentication builtin!

@lgaudreau
Copy link

@atomicangel, this was super helpful, thanks!

auth_request_set $target_url https://timetaggerurl.example.com/timetagger/login;

With this set up, do you still need to click on the 'submit' button on the login page? So far I can get a web token when signing in with Authelia, but still need to login with a blank username and password - it will log in with the username from Authelia though.

I have the redirect set in the run.py file for now. I'm using Nginx Proxy Manager so it's not exactly clear to me how to set the Nginx redirect.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants