{"id":13928830,"url":"https://github.com/rpgeeganage/ots-share-app","last_synced_at":"2025-03-19T21:30:39.319Z","repository":{"id":65815616,"uuid":"600543286","full_name":"rpgeeganage/ots-share-app","owner":"rpgeeganage","description":"A self-hosting app to share secrets only one-time.","archived":false,"fork":false,"pushed_at":"2024-01-11T16:10:38.000Z","size":799,"stargazers_count":62,"open_issues_count":5,"forks_count":3,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-02-28T23:21:17.214Z","etag":null,"topics":["app","application","nodejs","otp","otp-applications","otp-generator","otp-s","otp-share","password-sharing","secret-shares","secret-sharing","security-hardening","security-tools","self-hosted","side-project","side-projects","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rpgeeganage.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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}},"created_at":"2023-02-11T20:17:07.000Z","updated_at":"2024-12-08T03:04:51.000Z","dependencies_parsed_at":"2024-01-11T19:42:02.123Z","dependency_job_id":null,"html_url":"https://github.com/rpgeeganage/ots-share-app","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rpgeeganage%2Fots-share-app","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rpgeeganage%2Fots-share-app/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rpgeeganage%2Fots-share-app/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rpgeeganage%2Fots-share-app/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rpgeeganage","download_url":"https://codeload.github.com/rpgeeganage/ots-share-app/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244056406,"owners_count":20390719,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["app","application","nodejs","otp","otp-applications","otp-generator","otp-s","otp-share","password-sharing","secret-shares","secret-sharing","security-hardening","security-tools","self-hosted","side-project","side-projects","typescript"],"created_at":"2024-08-07T18:01:51.796Z","updated_at":"2025-03-19T21:30:38.959Z","avatar_url":"https://github.com/rpgeeganage.png","language":"TypeScript","funding_links":[],"categories":["nodejs"],"sub_categories":[],"readme":"# ![favicon-3](https://user-images.githubusercontent.com/10336353/220982413-cfae31b0-cb1e-4dde-a031-42bac3696985.png) OTS-Share (One-Time Secret Share)\n\nA self-hosting app to share secrets only one-time.\n\n### Content\n\n- [ **Features** ](#features)\n- [ **How to run** ](#how-to-run)\n  - [ **With the default database**](#with-the-default-database)\n  - [ **Without the default database**](#without-the-default-database)\n  - [ **Access UI**](#access-ui)\n- [ **Request and response** ](#request-and-response)\n  - [ **Request** ](#request)\n  - [ **Response** ](#response)\n- [ **How to use** ](#how-to-use)\n  - [ **For text** ](#for-text)\n  - [ **For files** ](#for-files)\n  - [ **View secret content in the shared link** ](#view-secret-content-in-the-shared-link)\n  - [ **Errors** ](#errors)\n- [ **CLI usage**](#cli-usage)\n- [ **Change configurations** ](#change-configurations)\n  - [ **Change database server** ](#change-database-server)\n  - [ **Change the default server port** ](#change-the-default-server-port)\n  - [ **Change the purge process interval** ](#change-the-purge-process-interval)\n- [ **Tech stack** ](#tech-stack)\n- [ **Using with deploy tools**](#using-with-deploy-tools)\n  - [ **With https://www.portainer.io/**](#with-httpswwwportainerio)\n- [ **Format of the generated URL** ](#format-of-the-generated-url)\n- [ **Road map**](#road-map)\n- [ **Todo** ](#todo)\n\n## Features\n\n- Support both **texts** and **small files (maximum `1MB`)**.\n- Creates shareable links which valid for a maximum of **24 hours**.\n- The contents are encrypted with `AES` in `CBC` mode, with a `256-bit` key.\n  (Using [Crypto-js](https://cryptojs.gitbook.io/docs/#the-cipher-algorithms))\n- Passwords are **NOT** sent to the backend server.\n- The app periodically deletes encrypted content after it expires, and the encrypted content gets deleted once the web UI fetches it.\n- CLI support.\n- Multiple database connectivity support.\n  - `Mongo`\n  - `Postgres`\n  - `MySQL`\n\n## How to run\n\n### With the default database\n\nThis application is entirely run in Docker and comes with `Mongo 4.2` image. (view the `docker-compose.yml` for further reference.)\n\nTo execute this app, simply run following command.\n\n```bash\n\nmake start\n\n```\n\n### Without the default database\n\nThis application can connect to a external database.\n(Currently support **`Postgres`** and **`Mysql`**).\n\nTo execute this app, simply run following command.\n\n```bash\n# Set the connection string to your database.\nexport DB_URL=mysql://root:root@host.docker.internal:3306/ots_share\n\nmake start-no-db\n```\n\n**OR**\n\nChange the modify the `DB_URL` variable under `ots-share-run-no-db` service in `docker-compose.yml`,\nand then run\n\n```bash\nmake start-no-db\n```\n\n### Access UI\n\nAfter that, the application is accessible via [http://localhost:8282](http://localhost:8282)\n\n## Request and Response\n\n### Request\n\n### Create record request body\n\nA sample request body is as follows.\n\n```json\n{\n  \"content\": \"U2FsdGVkX1+XUedzb2748LeKmf9UpN9hVWjBDUwJfXs=\",\n  \"expireIn\": {\n    \"value\": 10,\n    \"unit\": \"minutes\"\n  }\n}\n```\n\n| Property           | type                         | is required | purpose                                 |\n| ------------------ | ---------------------------- | ----------- | --------------------------------------- |\n| `content`          | `string`                     | yes         | Encrypted content                       |\n| `expireIn`         | `object`                     | yes         | Expiration configurations               |\n| `expireIn`.`value` | `number`                     | yes         | numerical value of expiration. E,g 1, 2 |\n| `expireIn`.`unit`  | `enum` (`'days'`, `'hours'`) | yes         | Unit of expiration.                     |\n\n- ### Sample `Create` request.\n\n```sh\ncurl 'http://localhost:8282/api/record' -H 'Content-Type: application/json' \\\n --data-raw \\\n '{\n    \"content\" : \"U2FsdGVkX1+bozD8VjexiUeHJ3BfdxrXCmRyai8V0hY=\",\n    \"expireIn\": {\n      \"value\": 1,\n      \"unit\": \"minutes\"\n    }\n  }'\n--compressed\n\n```\n\n- ### Sample `GET` request.\n\n```sh\ncurl 'http://localhost:8282/api/record/b2nC422huavXfMs2DWZ2Z9' -H 'Content-Type: application/json'\n```\n\n### Response\n\nA sample record body is as follows.\n\n```json\n{\n  \"id\": \"iN2jS3y1pstio7JVXs1zLF\",\n  \"slug\": \"iN2jS3y1pstio7JVXs1zLF\",\n  \"content\": \"U2FsdGVkX1+XUedzb2748LeKmf9UpN9hVWjBDUwJfXs=\",\n  \"expiary\": \"2023-02-12T14:55:41.510Z\",\n  \"status\": \"avaiable\",\n  \"created_at\": \"2023-02-12T14:45:41.521Z\"\n}\n```\n\n| Property     | type                                  | is required | purpose                                    |\n| ------------ | ------------------------------------- | ----------- | ------------------------------------------ |\n| `id`         | `string`                              | yes         | Primary key of the record                  |\n| `slug`       | `string`                              | yes         | For future use (Primary key of the record) |\n| `content`    | `string`                              | yes         | Encrypted content                          |\n| `expiary`    | `string` (`Date`)                     | yes         | Expiration date and time                   |\n| `status`     | `enum` (`'avaiable'`, `'unavaiable'`) | yes         | For future use.                            |\n| `created_at` | `string` (`Date`)                     | yes         | Record created date                        |\n\n## How to use\n\n# (Please don't lose the generated URL. There is no way to retrieve the content or regenerate the URL !!!)\n\n### For text\n\n1. Visit the landing page and select `Text` from top menu.\n2. Add your secret content to the `Secret content` text box.\n\n![text-1](https://user-images.githubusercontent.com/10336353/220972813-7507371c-8db1-442f-86e5-31d2b574ff10.png)\n\n3. Click the `Create` Button.\n4. Copy the `URL` in the text box. (Click the `Copy Icon`).\n   ![Screenshot (2)](https://user-images.githubusercontent.com/10336353/218278298-2ded1d50-82e0-4cbf-978f-79f9d637876f.png)\n\n- (Please don't lose the generated URL. There is no way to retrieve the content or regenerate the URL !!!)\n\n5. Send the copied URL to the other party via a secure channel.\n\n### For files\n\n1. Visit the landing page and select `File` from top menu.\n2. Click on the `\"Drag 'n' drop\"` area or drag and drop a file.\n\n- (Pleas refer to screen regarding the file size limits).\n\n  ![file-1](https://user-images.githubusercontent.com/10336353/220973983-0c6a84d3-2fb6-4289-960d-1eb080c461e5.png)\n\n3. Upload a file.\n   ![file-2](https://user-images.githubusercontent.com/10336353/220975812-ce433113-1faf-4bc0-be60-6e23bc944c11.png)\n\n4. Click the `Create` Button.\n5. Copy the `URL` in the text box. (Click the `Copy Icon`).\n   ![file-3](https://user-images.githubusercontent.com/10336353/220976372-e99047e8-4c94-4735-b006-5fb487fd8c63.png)\n\n- (Please don't lose the generated URL. There is no way to retrieve the content or regenerate the URL !!!)\n\n5. Send the copied URL to the other party via a secure channel.\n\n### View secret content in the shared link.\n\n1. Visit the shared link using a browser.\n2. You will see the following screen.\n   ![Screenshot (3)](https://user-images.githubusercontent.com/10336353/218278430-8dfc4b41-1f75-4a67-a3e0-a2966b3d57fa.png)\n3. Click `Fetch Content`.\n\n#### For text\n\n4. You'll see the following screen.\n   ![Screenshot (4)](https://user-images.githubusercontent.com/10336353/218278478-15c40978-116b-4f73-868b-8deaf4eb1b86.png)\n\n5. Click the `\"Click there to view the content\"`.\n\n6. You will see the content as follows.\n   ![Screenshot (5)](https://user-images.githubusercontent.com/10336353/218278542-0979fda7-afa0-4425-99c2-a283bcc3e3d1.png)\n\n#### For files\n\n4. You'll see the following screen.\n   ![file-4](https://user-images.githubusercontent.com/10336353/220977354-e8a2fbdd-5422-4c85-9962-e50d54f27615.png)\n\n5. Click the `\"Click here to download the file\"` button to download the file.\n\n### Errors.\n\nIn case of an error, the following screen will appear.![Screenshot (6)](https://user-images.githubusercontent.com/10336353/218278571-9af87297-0e2c-44dd-b172-c5ddbe28a7f3.png)\n\n## CLI usage\n\n- Support only for `texts`.\n- You can use the `CLI` to utilize `APIs`.\n\n- Encryption using CLI\n\u003cdetails\u003e\n  \u003csummary\u003eSample CLI to use encryption\u003c/summary\u003e\n\n```sh\n#!/bin/bash\n\n# Configs\nPASSWORD=\"pass-key\"\nOTS_SHARE_DOMIN=\"http://host.docker.internal:8282\"\n\nOTS_SHARE_API=\"$OTS_SHARE_DOMIN/api/record\"\n\nOPENSSL_PARAMETERS_PASSWORD=\"-pass pass:$PASSWORD\"\nOPENSSL_PARAMETERS_ALGORITHM=\"-base64 -aes-256-cbc -pbkdf2\"\n\ntext_to_encrypt=\"test string to encrypt\"\n\n################\n## Encryption ##\n################\n\n# Record expiration value. A numerical value\nRECORD_EXPIRATION_VALUE=10\n# Record expiration unit. It can be \"minutes\" or \"hours\"\nRECORD_EXPIRATION_UNIT=\"minutes\"\n\n# 1. Generate encrypted string\nencrypted_content=$(echo $text_to_encrypt | openssl enc -e $OPENSSL_PARAMETERS_ALGORITHM $OPENSSL_PARAMETERS_PASSWORD)\n\n# 2. Make API call OTS-Share and retrieve the Id\n# We need this id for encryption\nrecord_id=$(\\\ncurl -s \"$OTS_SHARE_API\" \\\n-H 'Content-Type: application/json' \\\n--data-raw \\\n'{ \"content\" : \"'$encrypted_content'\", \"expireIn\": { \"value\": '$RECORD_EXPIRATION_VALUE', \"unit\": \"'$RECORD_EXPIRATION_UNIT'\" }}' \\\n--compressed \\\n| jq '.id' \\\n| tr -d '\"' \\\n)\n#### Encryption results\necho \"!!! Keep these safe !!!\"\necho \"-----------------------------------\"\necho \"Record id: $record_id\"\necho \"Password: $PASSWORD\"\necho \"-----------------------------------\"\necho \"(This record will expires in: $RECORD_EXPIRATION_VALUE $RECORD_EXPIRATION_UNIT)\"\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eOutput encryption\u003c/summary\u003e\n\n```sh\n!!! Keep these safe !!!\n-----------------------------------\nRecord id: b2nC422huavXfMs2DWZ2Z9\nPassword: pass-key\n-----------------------------------\n(This record will expires in: 10 minutes)\n```\n\n\u003c/details\u003e\n\n- Decryption using CLI\n\n\u003cdetails\u003e\n  \u003csummary\u003eSample CLI to use Decryption\u003c/summary\u003e\n\n```sh\n#!/bin/bash\n\n# Configs\nPASSWORD=\"pass-key\"\nOTS_SHARE_DOMIN=\"http://host.docker.internal:8282\"\n\nOTS_SHARE_API=\"$OTS_SHARE_DOMIN/api/record\"\n\nOPENSSL_PARAMETERS_PASSWORD=\"-pass pass:$PASSWORD\"\nOPENSSL_PARAMETERS_ALGORITHM=\"-base64 -aes-256-cbc -pbkdf2\"\n\n$record_id=\"b2nC422huavXfMs2DWZ2Z9\" # ID from previous encryption operation\n\n################\n## DECRYPTION ##\n################\n\n# 1. Fetch content\ncontent=$(\\\ncurl \"$OTS_SHARE_API/$record_id\" \\\n-s -H 'Content-Type: application/json' \\\n--compressed \\\n| jq '.content' \\\n| tr -d '\"' \\\n)\n\n# 2. Decrypt\ndecrypted_content=$(echo $content | openssl enc -d $OPENSSL_PARAMETERS_ALGORITHM $OPENSSL_PARAMETERS_PASSWORD)\n\necho \"-----------------------------------\"\necho \"Content: $decrypted_content\"\necho \"-----------------------------------\"\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eOutput decryption\u003c/summary\u003e\n\n```sh\n-----------------------------------\nContent: test string to encrypt\n-----------------------------------\n```\n\n\u003c/details\u003e\n\n###\n\n## Change configurations\n\n### All the configurations are mentioned in the `docker-compose.yml` under or `ots-share-run-no-db` service.\n\n- Change default port to access the application are available in `docker-compose.yml` under `ots-share-run` or `ots-share-run-no-db` service.\n- You can modify the `mongo-local` service in `docker-compose.yml` to keep the data persistent.\n\n#### Change database server.\n\n- Please change the `DEV_PORT` variable the `docker-compose.yml` under `ots-share-run` or `ots-share-run-no-db` service.\n\n- Please change the `DEV_PORT` variable the `docker-compose.yml` under `ots-share-run-no-db` service to connect to external database.\n\n- `DB_URL` must be a connection string.\n  - The app parse the `DB_URL` as an `URL` and use the `protocol` to identify the `database` driver.\n  - sample connection strings:\n    - `mongodb://mongo-local/ots-share` - for `Mongo`\n    - `postgres://db:db@host.docker.internal:5432/ots_share` - for `Postgres`\n    - `mysql://root:root@host.docker.internal:3306/ots_share` - for `MySQL`\n\n#### Change the default server port.\n\n- Please change `SERVER_PORT` variable in the in `docker-compose.yml` under `ots-share-run` or `ots-share-run-no-db` service.\n\n#### Change the purge process interval.\n\n- Default value is 1 minute.\n- Please set `PURGE_TRIGGER_INTERVAL` variable in the in `docker-compose.yml` under `ots-share-run` or `ots-share-run-no-db` service.\n- The `PURGE_TRIGGER_INTERVAL` value must be in `milliseconds`.\n\n## Tech stack\n\n- **UI:**\n\n  - React\n  - Material UI\n\n- **Server:**\n\n  - Typescript\n  - Node\n  - Express\n\n- **DB support:**\n  - `MongoDB` - (`default DB`)\n  - `Postgres`\n  - `MySQL`\n\n## Using with deploy tools\n\n### With **https://www.portainer.io/**\n\n- Step 1 \u0026 2.\n  ![step1](https://user-images.githubusercontent.com/10336353/220198712-7d379d69-3fdb-430a-93f6-c02c0d6b4ae9.png)\n\n- Step 3.\n  ![step 2](https://user-images.githubusercontent.com/10336353/220198843-8be34f58-f7dd-465d-b59a-7e9b4764ffc0.png)\n\n- Press **Deploy the stack** to deploy.\n\n## Format of the generated URL\n\nThe URL format, which required sending to the other party, is as follows. The `id` received from the backend API gets concatenated with the password. After that, the contaminated string gets encoded into `Base 58`.\n\nThe format is as follows.\n\n- `\u003chosted-domain\u003e/r/Base58Encoded(id-from-api : password : type : file-name)`\n- Possible values for `type` is `'text'` or `'file'`.\n- `type` and `file-name` is optional.\n- `type` is default to `text` if not mentioned.\n- `file-name` is available for file.\n- It supports `Base 64` encoding.\n\n## Road map\n\n- A Chrome extension\n- A Slack app\n\n## Todo\n\n- Add `Contribution` instructions.\n- Learn more ReactJs. :smile:\n- Fix any bugs. :smile:\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frpgeeganage%2Fots-share-app","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frpgeeganage%2Fots-share-app","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frpgeeganage%2Fots-share-app/lists"}