Security: Log User Login / Logout #739

Open
opened 2026-02-19 23:15:26 -05:00 by deekerman · 18 comments
Owner

Originally created by @y0grt on GitHub (Jan 23, 2021).

Originally assigned to: @lastzero on GitHub.

Include this information in log output in order to block IPs with too many attemps.
This can be implemented in Photoprism itself, but simply with this information in the log output existing solutions like fail2ban can be used for this purpose.

Originally created by @y0grt on GitHub (Jan 23, 2021). Originally assigned to: @lastzero on GitHub. Include this information in log output in order to block IPs with too many attemps. This can be implemented in Photoprism itself, but simply with this information in the log output existing solutions like fail2ban can be used for this purpose.
Author
Owner

@lastzero commented on GitHub (Jan 23, 2021):

Did you try a brute force attack?

@lastzero commented on GitHub (Jan 23, 2021): Did you try a brute force attack?
Author
Owner

@y0grt commented on GitHub (Jan 23, 2021):

Not a proper one.
Simply tried like 10 times with wrong credentials and debug enabled, but couldn't see anything on the log output.
I couldn't find any information either.

@y0grt commented on GitHub (Jan 23, 2021): Not a proper one. Simply tried like 10 times with wrong credentials and debug enabled, but couldn't see anything on the log output. I couldn't find any information either.
Author
Owner

@lastzero commented on GitHub (Jan 23, 2021):

How long did you have to wait for each attempt?

@lastzero commented on GitHub (Jan 23, 2021): How long did you have to wait for each attempt?
Author
Owner

@y0grt commented on GitHub (Jan 23, 2021):

I've realised about the throttling on the login process, it's a mitigation indeed.
But would be great to be able to provide a more fine grain control, like to be able to trigger a full IP block through iptables rules.
I discovered as well that the username can be easily guessed since an existing username triggers an instant fail (400 from /api/v1/session).

@y0grt commented on GitHub (Jan 23, 2021): I've realised about the throttling on the login process, it's a mitigation indeed. But would be great to be able to provide a more fine grain control, like to be able to trigger a full IP block through iptables rules. I discovered as well that the username can be easily guessed since an existing username triggers an instant fail (400 from /api/v1/session).
Author
Owner

@lastzero commented on GitHub (Jan 23, 2021):

Well, there's only one user right now - so that'd be easy to guess anyway. Logging logins is OK if not all users can see this as it also leaks information. Brute force should not be an issue with each attempt getting slower.

@lastzero commented on GitHub (Jan 23, 2021): Well, there's only one user right now - so that'd be easy to guess anyway. Logging logins is OK if not all users can see this as it also leaks information. Brute force should not be an issue with each attempt getting slower.
Author
Owner

@pjft commented on GitHub (Jun 14, 2021):

Came here to ask about the same. Certainly throttling is useful, but given that we're exposing this to the outside world, I'd be more confident monitoring attempts and being able to jail those IPs. Not to mention that given that the username is hardcoded to "admin", they only have to guess a password.

@pjft commented on GitHub (Jun 14, 2021): Came here to ask about the same. Certainly throttling is useful, but given that we're exposing this to the outside world, I'd be more confident monitoring attempts and being able to jail those IPs. Not to mention that given that the username is hardcoded to "admin", they only have to guess a password.
Author
Owner

@lastzero commented on GitHub (Jun 14, 2021):

Try guessing the password and see what happens 🌼

@lastzero commented on GitHub (Jun 14, 2021): Try guessing the password and see what happens 🌼
Author
Owner

@lastzero commented on GitHub (Jul 22, 2021):

Took a look at this issue again due to popular demand:

  • Login attempts are already counted in the users.login_attempts column, you'll also find a login_at timestamp in the users table which shows when the last successful login was.
  • If you use a reverse proxy in front of PhotoPrism (which you should if connected to the public internet), you can monitor the POST /api/v1/session API endpoint for error 400 responses as then block the source IP. No need to reinvent the wheel.
  • PhotoPrism's logs don't contain personal identifying information as they are visible by anyone, especially on our demo. That way, IPs of others users may leak which poses a privacy risk.
  • From my understanding, the only way to implement login logs with IP addresses in a secure way is to introduce personalized log feeds or different logs depending on authorization. While that's certainly possible, doing it now (or providing guidance for contributors how to implement this) would delay our work on the items we already selected for development on our roadmap. Thus I would only do this if it's an emergency, for example because there's a good reason proxy request logs can't be used for this purpose?
@lastzero commented on GitHub (Jul 22, 2021): Took a look at this issue again due to popular demand: - Login attempts are already counted in the `users.login_attempts` column, you'll also find a `login_at` timestamp in the `users` table which shows when the last successful login was. - If you use a reverse proxy in front of PhotoPrism (which you should if connected to the public internet), you can monitor the `POST /api/v1/session` API endpoint for error 400 responses as then block the source IP. No need to reinvent the wheel. - PhotoPrism's logs don't contain personal identifying information as they are visible by anyone, especially on our demo. That way, IPs of others users may leak which poses a privacy risk. - From my understanding, the only way to implement login logs with IP addresses in a secure way is to introduce personalized log feeds or different logs depending on authorization. While that's certainly possible, doing it now (or providing guidance for contributors how to implement this) would delay our work on the items we already selected for development on our roadmap. Thus I would only do this if it's an emergency, for example because there's a good reason proxy request logs can't be used for this purpose?
Author
Owner

@pjft commented on GitHub (Jul 22, 2021):

Thanks.
I don't think it's an emergency at all. It's important, not urgent.
As far as the reverse proxy suggestion goes, may that's an option, but as an individual user I don't think I have a reverse proxy set up.
I run things from a Raspberry Pi, and use fail2ban to block login attempts to the only services that are exposed externally. I genuinely don't know what other users use or not and their network configuration, but fail2ban seems to be the normally-recommended IP/login blocking tool for anything from SSH, OpenVPN, Nextcloud and other services I run on Linux, docker or not. It monitors a logfile and creates a jail for specific IPs - many in syslog, others in other app-specific log files.
I don't know what others use, so maybe I'm in the minority here. I see @jmlgo seems to run a similar setup.
But, as I said, I personally don't think it's an emergency. I appreciate the flexibility and openness in exposing the multiple scenarios, and I understand the challenge of leaking the IPs in the demo environment.
I don't know where the actual logfile is stored right now, for me to even explore options there - is it all stored in MariaDB?
Thanks.

@pjft commented on GitHub (Jul 22, 2021): Thanks. I don't think it's an emergency at all. It's important, not urgent. As far as the reverse proxy suggestion goes, may that's an option, but as an individual user I don't think I have a reverse proxy set up. I run things from a Raspberry Pi, and use [fail2ban](https://www.fail2ban.org/wiki/index.php/Main_Page) to block login attempts to the only services that are exposed externally. I genuinely don't know what other users use or not and their network configuration, but fail2ban seems to be the normally-recommended IP/login blocking tool for anything from SSH, OpenVPN, Nextcloud and other services I run on Linux, docker or not. It monitors a logfile and creates a jail for specific IPs - many in syslog, others in other app-specific log files. I don't know what others use, so maybe I'm in the minority here. I see @jmlgo seems to run a similar setup. But, as I said, I personally don't think it's an emergency. I appreciate the flexibility and openness in exposing the multiple scenarios, and I understand the challenge of leaking the IPs in the demo environment. I don't know where the actual logfile is stored right now, for me to even explore options there - is it all stored in MariaDB? Thanks.
Author
Owner

@lastzero commented on GitHub (Jul 22, 2021):

The logs are sent to Docker (run docker-compose logs --tail=25 -f to see them) as well as browsers via websocket if PhotoPrism is running in public mode or users are authenticated. As explained above, there isn't a separate log for each user or browser at the moment.

If you don't (want to) use Docker, you may send logs to a file using the config option PHOTOPRISM_LOG_FILENAME, see https://docs.photoprism.org/getting-started/config-options/.

Note that there should always be an HTTPS reverse proxy in place when using PhotoPrism outside a secure network, so that your password and files won't be sent in plain text over the public internet. That's a huge security issue in itself. Otherwise you also have to use a separate port for each app, which is a major inconvenience.

@lastzero commented on GitHub (Jul 22, 2021): The logs are sent to Docker (run `docker-compose logs --tail=25 -f` to see them) as well as browsers via websocket if PhotoPrism is running in public mode or users are authenticated. As explained above, there isn't a separate log for each user or browser at the moment. If you don't (want to) use Docker, you may send logs to a file using the config option `PHOTOPRISM_LOG_FILENAME`, see https://docs.photoprism.org/getting-started/config-options/. Note that there should always be an HTTPS reverse proxy in place when using PhotoPrism outside a secure network, so that your password and files won't be sent in plain text over the public internet. That's a huge security issue in itself. Otherwise you also have to use a separate port for each app, which is a major inconvenience.
Author
Owner

@lastzero commented on GitHub (Jul 22, 2021):

In a future release, we may implement a separate security log (file) for messages that shouldn't go to the regular logs. Ideally in a standard format consumable by tools like "fail2ban". You're welcome to share specific requirements for this.

@lastzero commented on GitHub (Jul 22, 2021): In a future release, we may implement a separate security log (file) for messages that shouldn't go to the regular logs. Ideally in a standard format consumable by tools like "fail2ban". You're welcome to share specific requirements for this.
Author
Owner

@pjft commented on GitHub (Jul 22, 2021):

Thank you for the explanation. I am using docker, this helps.

I'll try to look into setting up some sort of reverse proxy - hadn't considered the http/https issue, but it's a completely fair remark. I will look into setting it up there, as I have nginx running locally.

I don't have any specific suggestion for the log format. Usually it's something that has the date/time, type of log, description text, user and source. Something like:

2021-07-17 02:58:15 WARNING (photoprism) Login attempt or request with invalid authentication from 192.168.86.14 for user 'admin'.

could probably work - fail2ban uses regexp, so I'm sure it'd just be a matter of figuring the right one out.

Thank you, and keep up the great work.

@pjft commented on GitHub (Jul 22, 2021): Thank you for the explanation. I am using docker, this helps. I'll try to look into setting up some sort of reverse proxy - hadn't considered the http/https issue, but it's a completely fair remark. I will look into setting it up there, as I have nginx running locally. I don't have any specific suggestion for the log format. Usually it's something that has the date/time, type of log, description text, user and source. Something like: 2021-07-17 02:58:15 WARNING (photoprism) Login attempt or request with invalid authentication from 192.168.86.14 for user 'admin'. could probably work - fail2ban uses regexp, so I'm sure it'd just be a matter of figuring the right one out. Thank you, and keep up the great work.
Author
Owner

@pjft commented on GitHub (Jul 26, 2021):

@lastzero Apologies, just came here to thank you for the reverse proxy tip. It had been quite understated when reading the documentation, but indeed - especially after you spelled out the challenge with http - it made perfect sense.

I already had existing certificates and nginx running, so it was just a matter of setting it up appropriately to use the certificates. Took me 30 mins or so on Saturday, without having ever done so in the past, and even though the HTTP to HTTPS redirect isn't quite working, at least it no longer works on HTTP and I just need to access HTTPS explicitly, which is completely fine for me.

Thank you!

For anyone who needs guidance setting up HTTPS, this reddit thread was helpful, and I imagine that if you don't have a certificate, then this might help (untested).

@pjft commented on GitHub (Jul 26, 2021): @lastzero Apologies, just came here to thank you for the reverse proxy tip. It had been quite understated when reading the documentation, but indeed - especially after you spelled out the challenge with http - it made perfect sense. I already had existing certificates and nginx running, so it was just a matter of setting it up appropriately to use the certificates. Took me 30 mins or so on Saturday, without having ever done so in the past, and even though the HTTP to HTTPS redirect isn't quite working, at least it no longer works on HTTP and I just need to access HTTPS explicitly, which is completely fine for me. Thank you! For anyone who needs guidance setting up HTTPS, [this reddit thread was helpful](https://www.reddit.com/r/selfhosted/comments/hx1b3s/photoprism_how_to_enable_https/), and I imagine that if you don't have a certificate, then [this might help](https://www.nginx.com/blog/using-free-ssltls-certificates-from-lets-encrypt-with-nginx/) (untested).
Author
Owner

@F1orian commented on GitHub (Aug 23, 2022):

For anyone interested: I got fail2ban to work with PhotoPrism by directing it at the nginx access.log of my reverse proxy.
I'm using the following filter in fail2ban:

[Definition]
failregex = ^<HOST>.*POST\s\/api\/v1\/session\sHTTP.*\s400\s.*https:\/\/[INSERT YOUR DOMAIN HERE]\/auth\/login.*
datepattern = \s-\s-\s\[%%d\/%%b\/%%Y\:%%H:%%M:%%S\s%%z\]\s"
@F1orian commented on GitHub (Aug 23, 2022): For anyone interested: I got fail2ban to work with PhotoPrism by directing it at the nginx access.log of my reverse proxy. I'm using the following filter in fail2ban: ``` [Definition] failregex = ^<HOST>.*POST\s\/api\/v1\/session\sHTTP.*\s400\s.*https:\/\/[INSERT YOUR DOMAIN HERE]\/auth\/login.* datepattern = \s-\s-\s\[%%d\/%%b\/%%Y\:%%H:%%M:%%S\s%%z\]\s" ```
Author
Owner

@thelpfy commented on GitHub (Sep 6, 2023):

For anyone interested: I got fail2ban to work with PhotoPrism by directing it at the nginx access.log of my reverse proxy. I'm using the following filter in fail2ban:

[Definition]
failregex = ^<HOST>.*POST\s\/api\/v1\/session\sHTTP.*\s400\s.*https:\/\/[INSERT YOUR DOMAIN HERE]\/auth\/login.*
datepattern = \s-\s-\s\[%%d\/%%b\/%%Y\:%%H:%%M:%%S\s%%z\]\s"

With the latest docker image and swag i got it to work with:

[INCLUDES]
before = common.conf

[Definition]
failregex = ^<HOST>.*POST\s\/api\/v1\/session\sHTTP.*\s401\s.*https:\/\/[INSERT DOMAIN HERE]\/library\/login.*
datepattern = \s-\s-\s\[%%d\/%%b\/%%Y\:%%H:%%M:%%S\s%%z\]\s"

So i had to change the response code to 401 and swap "auth" with "library"

@thelpfy commented on GitHub (Sep 6, 2023): > For anyone interested: I got fail2ban to work with PhotoPrism by directing it at the nginx access.log of my reverse proxy. I'm using the following filter in fail2ban: > > ``` > [Definition] > failregex = ^<HOST>.*POST\s\/api\/v1\/session\sHTTP.*\s400\s.*https:\/\/[INSERT YOUR DOMAIN HERE]\/auth\/login.* > datepattern = \s-\s-\s\[%%d\/%%b\/%%Y\:%%H:%%M:%%S\s%%z\]\s" > ``` With the latest docker image and swag i got it to work with: ``` [INCLUDES] before = common.conf [Definition] failregex = ^<HOST>.*POST\s\/api\/v1\/session\sHTTP.*\s401\s.*https:\/\/[INSERT DOMAIN HERE]\/library\/login.* datepattern = \s-\s-\s\[%%d\/%%b\/%%Y\:%%H:%%M:%%S\s%%z\]\s" ``` So i had to change the response code to 401 and swap "auth" with "library"
Author
Owner

@pjft commented on GitHub (Sep 7, 2023):

Thank you @TheLPfy ! Works great.

@pjft commented on GitHub (Sep 7, 2023): Thank you @TheLPfy ! Works great.
Author
Owner

@klack commented on GitHub (Aug 2, 2024):

[Definition]
failregex = ^<HOST> \- \S+ \[\] \"(GET|POST|HEAD) \/photos/api/v1/session \S+\" 4\d{2} .+$

Here is what I ended up using with the Traefik access log for all 400 errors

@klack commented on GitHub (Aug 2, 2024): ``` [Definition] failregex = ^<HOST> \- \S+ \[\] \"(GET|POST|HEAD) \/photos/api/v1/session \S+\" 4\d{2} .+$ ``` Here is what I ended up using with the Traefik access log for all 400 errors
Author
Owner

@jbouwens commented on GitHub (Apr 17, 2025):

PhotoPrism users who use Caddy 2 can add logging to Caddyfile config:

https://test.example.com {
    log {
        output file /config/photoprism_access.log
        format json
    }
    
    reverse_proxy photoprism-ip:2342
}

Then for the filter, photoprism-auth.conf:

[INCLUDES]
before = common.conf

[Definition]
failregex = ^.*"remote_ip":"<HOST>".*"method":"POST".*"uri":"\/api\/v1\/session".*"status":401.*$
ignoreregex =

datepattern = "ts":%%s
@jbouwens commented on GitHub (Apr 17, 2025): PhotoPrism users who use Caddy 2 can add logging to Caddyfile config: ``` https://test.example.com { log { output file /config/photoprism_access.log format json } reverse_proxy photoprism-ip:2342 } ``` Then for the filter, photoprism-auth.conf: ``` [INCLUDES] before = common.conf [Definition] failregex = ^.*"remote_ip":"<HOST>".*"method":"POST".*"uri":"\/api\/v1\/session".*"status":401.*$ ignoreregex = datepattern = "ts":%%s ```
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/photoprism#739
No description provided.