{"id":30788704,"url":"https://github.com/brews/wenceslas","last_synced_at":"2026-07-12T00:32:17.065Z","repository":{"id":312188214,"uuid":"1046622746","full_name":"brews/wenceslas","owner":"brews","description":"A small web service to verify user emails and passwords against stored Wordpress password hashes.","archived":false,"fork":false,"pushed_at":"2026-03-25T00:28:20.000Z","size":128,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-03-31T06:32:15.582Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/brews.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-08-29T01:02:52.000Z","updated_at":"2026-03-25T00:28:24.000Z","dependencies_parsed_at":null,"dependency_job_id":"22e91be6-2782-4491-92dc-830e5bd003b5","html_url":"https://github.com/brews/wenceslas","commit_stats":null,"previous_names":["brews/wenceslas"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/brews/wenceslas","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brews%2Fwenceslas","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brews%2Fwenceslas/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brews%2Fwenceslas/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brews%2Fwenceslas/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/brews","download_url":"https://codeload.github.com/brews/wenceslas/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brews%2Fwenceslas/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35378722,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-11T02:00:05.354Z","response_time":104,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-09-05T13:52:36.430Z","updated_at":"2026-07-12T00:32:17.060Z","avatar_url":"https://github.com/brews.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# wenceslas\n\n[![Cargo Build \u0026 Test](https://github.com/brews/wenceslas/actions/workflows/rust.yml/badge.svg)](https://github.com/brews/wenceslas/actions/workflows/rust.yml)\n[![codecov](https://codecov.io/github/brews/wenceslas/graph/badge.svg?token=NVQ9H272M5)](https://codecov.io/github/brews/wenceslas)\n[![container](https://github.com/brews/wenceslas/actions/workflows/container.yml/badge.svg)](https://github.com/brews/wenceslas/actions/workflows/container.yml)\n\nA small web service to verify user emails and passwords against stored Wordpress password hashes.\n\n\nThe server responds with a verified/unverified decision whenever an email and\nraw password are POSTed to the `/verify` endpoint. The server loads a CSV file\nholding emails and Wordpress-hashed passwords into memory on startup.\n\nThe server can only verify requests against hashes from newer Wordpress-flavored\nbcrypt - the default in Wordpress 6.8 - and earlier Wordpress versions using\nphpass. These hashes have a `$P$` or `$wp` prefix. The server will fail on\nstartup if the CSV file has hashes with unknown prefixes.\n\nThe CSV file must be UTF-8 encoded with a header and columns giving `user_email`\nand `user_pass`. No duplicate user emails are allowed and will cause the server to fail on startup.\n\nThe server will also respond with user profiles to GET requests to `/users` with an email parameter.\n\n\u003e [!WARNING]\n\u003e This application does not have features to securely run alone in a production environment.\n\n## Example\n\nGenerate a CSV file with user email addresses and hashed passwords in an example directory.\n\n```shell\n  EXAMPLE_DIR=\"./exampledata\"\n  DB_FILE=\"wp_db.csv\"\n  DB_PATH=\"${EXAMPLE_DIR}/${DB_FILE}\"\n\n  mkdir $EXAMPLE_DIR\n\n  echo 'user_email,user_pass' \u003e $DB_PATH\n  echo 'johndoe@example.com,$wp$2y$10$gN3SQdbNc/cVlK7DylUiVumiuujud7lR0h5J4M2ZsNRMYOFbED16q' \u003e\u003e $DB_PATH\n  echo 'janedoe@example.com,$P$BsSozX7pxy0bajB//ff34WOg4vN9OI/' \u003e\u003e $DB_PATH\n```\n\nNow start the server. Here, we're using the container.\n\n```shell\n  docker run --rm  -p 127.0.0.1:8000:8000 \\\n    -v \"${EXAMPLE_DIR}:/data:ro\" \\\n    -e CSV_PATH=\"/data/${DB_FILE}\" \\\n    -e HOST=\"0.0.0.0\" \\\n    -e PORT=\"8000\" \\\n    ghcr.io/brews/wenceslas:latest\n```\n\u003e [!TIP]\n\u003e This example is using the `latest` tag for demonstration. In a production environment best practice is to use a tag or hash for a specific released version, such as `1.0.0`. Tagged images are available [here](https://github.com/brews/wenceslas/pkgs/container/wenceslas).  \n\nSend requests to the server like\n\n```shell\n  curl -X POST \"http://localhost:8000/verify\" \\\n    -H 'Content-Type: application/json' \\\n    --data-raw '{\"email\": \"johndoe@example.com\", \"password\": \"Test123Now!\"}'\n```\n\nand the server responds with\n\n```shell\n  {\"verified\":true}\n```\n\nA request with bad email or password like\n\n```shell\n  curl -X POST \"http://localhost:8000/verify\" \\\n    -H 'Content-Type: application/json' \\\n    --data-raw '{\"email\": \"janedoe@example.com\", \"password\": \"Test123Now!\"}'\n```\n\ngets the response\n\n```shell\n  {\"verified\":false}\n```\n\nPoorly formatted request bodies will get a (hopefully) descriptive error message and a 422 response status code.\n\nYou can set an optional API key when the server starts. The server will only allow POST requests with this key in the header of each request. For example, we can generate a length key like\n\n```shell\n  APIKEY=$(openssl rand -base64 40)\n```\n\nAnd pass the key to the server on startup like\n\n```shell\n  docker run --rm  -p 127.0.0.1:8000:8000 \\\n    -v \"${EXAMPLE_DIR}:/data:ro\" \\\n    -e CSV_PATH=\"/data/${DB_FILE}\" \\\n    -e HOST=\"0.0.0.0\" \\\n    -e PORT=\"8000\" \\\n    -e APIKEY=\"${APIKEY}\" \\\n    ghcr.io/brews/wenceslas:latest\n```\n\nAnd now requests need to include the key in the header of their requests like\n\n```shell\n  curl -X POST \"http://localhost:8000/verify\" \\\n    -H \"x-apikey: ${APIKEY}\" \\\n    -H 'Content-Type: application/json' \\\n    --data-raw '{\"email\": \"johndoe@example.com\", \"password\": \"Test123Now!\"}'\n```\n\nor the server will reply with \"401 Unauthorized\" response status.\n\nAgain, it's worth noting that using the API key feature does not secure network communication. Simply using the API key feature is not adequate security for running this in a production environment. \n\nYou can also use the `/user` endpoint to get user profiles. For example\n\n```shell\n  curl -X GET \"http://localhost:8000/users?email=johndoe%40example.com\" \\\n    -H \"x-apikey: ${APIKEY}\" \\\n    -H 'Content-Type: application/json'\n```\n\nis a request for the profile to johndoe@example.com and gets the response\n\n```\n  [{\"user_email\":\"johndoe@example.com\",\"display_name\":null,\"first_name\":null,\"last_name\":null,\"nickname\":null,\"organization\":null}]\n```\n\nThe response contains `null`s because these fields are optional, and not columns in the input CSV.\n\nThe response is a 404 if no profile is found for the requested email.\n\nSimilar information can be found with\n\n```shell\n  curl -X POST \"http://localhost:8000/email-query\" \\\n    -H \"x-apikey: ${APIKEY}\" \\\n    -H 'Content-Type: application/json'\n    --data-raw '{\"email\": \"johndoe@example.com\"}'\n```\n\nwhich gets the response\n\n```\n  {\"users\": [{\"user_email\":\"johndoe@example.com\",\"display_name\":null,\"first_name\":null,\"last_name\":null,\"nickname\":null,\"organization\":null}]}\n```\n\nThe `\"users\"` array is emtpy if there is no match. For example,\n\n```shell\n  curl -X POST \"http://localhost:8000/email-query\" \\\n    -H \"x-apikey: ${APIKEY}\" \\\n    -H 'Content-Type: application/json'\n    --data-raw '{\"email\": \"no@match.com\"}'\n```\n\ngets the response\n\n```\n  {\"users\": []}\n```\n\nThe benefit of POSTing to the `/email-query` endpoint over GETing the `/users` endpoint is that user emails in the POST request are not part of the URL, thus, less likely to be logged by intermediate systems, respecting user privacy.\n\n## Installation\n\nwenceslas is typically run from prebuilt container images.\n\n### Building from source\n\nTo build this from source you will need the Git Version Control System and the Rust toolchain.\n\nFirst clone the repository.\n\nCompile and run from source\n\n```shell\n# Use --locked for deterministic builds.\ncargo run --locked\n\n# Use --release for optimized builds without debugging symbols\n```\n\n### Local container build\n\nWith docker istalled and configured for your system, run\n\n```shell\n  docker build -t wenceslas:dev .\n```\n\nto build a container image tagged `wenceslas:dev`.\n\n## Configuration\n\nConfigurations are set through environment variables.\n\n- *CSV_PATH*: Required. Path to CSV file with user email and Wordpress password hashes. Must have columns and header `user_email` and `user_pass`.\n- *HOST*: Required. IPv4 or IPv6 address to listen for requests. E.g. \"127.0.0.1\" or \"::1\".\n- *PORT*: Required. Port over which to listen for requests. E.g. \"8000\".\n- *APIKEY*: Key to check for in request headers under the \"x-apikey\" field. No auth is used if this is not set.\n- *RUST_LOG*: Logging level. E.g. \"TRACE\".\n\n## Support\n\nwenceslas releases use semantic versioning. Best effort is made to note breaking changes to the application and its web API as documented. This does not include breaking changes to the application's Rust API.\n\nwenceslas is open-source software made available under the terms of either the MIT License or the Apache License 2.0, at your option.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrews%2Fwenceslas","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbrews%2Fwenceslas","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrews%2Fwenceslas/lists"}