{"id":13521238,"url":"https://github.com/takingnames/namedrop-protocol-spec","last_synced_at":"2025-03-31T20:31:05.457Z","repository":{"id":50538796,"uuid":"443263931","full_name":"takingnames/namedrop-protocol-spec","owner":"takingnames","description":"Specification for the NameDrop DNS delegation protocol","archived":false,"fork":false,"pushed_at":"2025-03-20T13:34:39.000Z","size":35,"stargazers_count":26,"open_issues_count":4,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-03-20T14:40:49.835Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/takingnames.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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":"2021-12-31T05:41:41.000Z","updated_at":"2025-03-20T13:34:42.000Z","dependencies_parsed_at":"2025-03-22T00:46:53.716Z","dependency_job_id":null,"html_url":"https://github.com/takingnames/namedrop-protocol-spec","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/takingnames%2Fnamedrop-protocol-spec","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/takingnames%2Fnamedrop-protocol-spec/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/takingnames%2Fnamedrop-protocol-spec/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/takingnames%2Fnamedrop-protocol-spec/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/takingnames","download_url":"https://codeload.github.com/takingnames/namedrop-protocol-spec/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246535818,"owners_count":20793327,"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":[],"created_at":"2024-08-01T06:00:31.310Z","updated_at":"2025-03-31T20:31:05.451Z","avatar_url":"https://github.com/takingnames.png","language":null,"readme":"# The NameDrop Protocol (draft version 0.4.0)\n\nNameDrop is developed by [TakingNames][0] for delegating control over DNS\ndomains and subdomains. It is an open protocol, and implementation by others\nis encouraged.\n\n\n# Overview\n\nNameDrop is based on [OAuth2][1], with a few additions to facilitate domain\nname delegation.\n\nOne key difference from how OAuth2 is generally implemented, is that client\nregistration with the authorization server is not required before initiating\ngrant flows. To maintain a level of security, it is required that the\n`client_id` be a prefix string of the `redirect_uri`, ie the `redirect_uri`\n(which is where the token ends up) must be on the same domain as the\n`client_id`. This method is essentially what is described [here][2]. NameDrop\nauthorization servers should display the `client_id` to users and inform them\nthat is who is requesting access.\n\nAll API endpoints described in this article are assumed to be appended to a\nbase URL. For example, TakingNames uses\n\n`https://takingnames.io/namedrop`\n\nIt is not necessary for the API name to start with `/namedrop`, but it can\nbe useful for namespacing if the server has other non-NameDrop endpoints.\n\n# OAuth2 scopes\n\nNameDrop scopes are prefixed with `namedrop-`, in order to facilitate\ncomposition with other OAuth2 protocols on the same authorization server.\n\nThe following scopes are currently specified:\n\n* `namedrop-hosts` - grants control over A, AAAA, CNAME, and ALIAS (aka ANAME) records\n* `namedrop-mail` - grants control over MX, DKIM TXT, and SPF TXT records\n* `namedrop-acme` - grants control over ACME TXT records\n* `namedrop-atproto-handle` - grants control over atproto `did=` TXT records\n  (see [here](https://atproto.com/specs/handle))\n\nPermissions are granted to a FQDN (domain or subdomain). Currently this works\nin a hierarchical fashion, ie if you have a token with permissions for\n`example.com`, you can create records for any subdomain of `example.com`\n(`*.example.com`). Likewise, if you have permissions for `sub.example.com`, you\ncan create records for `*.sub.example.com`, but not `example.com`.\n\n# OAuth2 endpoints\n\nThe basic OAuth2 endpoints are defined as follows:\n\n**`GET /authorize`**\n\nAuthorization endpoint (user consent to get code). Can be a web browser\nredirect, or a direct link, such as one printed from a CLI application.\n\n**`POST /token`**\n\nToken endpoint (swap code for token). Always server-to-server.\n\n\n# Token\n\nAccess tokens are returned as JSON in the following format:\n\n```javascript\n{\n  \"access_token\": String(),\n  \"refresh_token\": String(),\n  \"token_type\": \"bearer\",\n  \"expires_in\": Number(),\n  \"permissions\": [\n    {\n      \"scope\": String(),\n      \"domain\": String(),\n      \"host\": String(),\n    }\n  ]\n}\n```\n\nExample:\n\n```json\n{\n  \"access_token\": \"lkjaslkajsoidfnaiosnf\",\n  \"refresh_token\": \"iousdoinfoiseofinsef\",\n  \"token_type\": \"bearer\",\n  \"expires_in\": 3600,\n  \"permissions\": [\n    {\n      \"scope\": \"namedrop-hosts\",\n      \"domain\": \"example.com\",\n      \"host\": \"\"\n    },\n    {\n      \"scope\": \"namedrop-mail\",\n      \"domain\": \"example.com\",\n      \"host\": \"mail\"\n    },\n    {\n      \"scope\": \"namedrop-acme\",\n      \"domain\": \"example.com\",\n      \"host\": \"\"\n    }\n  ]\n}\n```\n\n# Getting and setting records\n\nRecords are managed via a simple RPC API. All requests use the POST method\nwith a JSON body. The `Content-Type` can be anything. This allows browser\nclients to send\n\"[simple](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests)\"\nrequests that don't trigger CORS preflights, which are\nan abomination. This is safe because all requests are authorized via the\nincluded token property.\n\nFor `create-records`, `set-records`, and `delete-records`, the top-level\n`domain` and `host` properties are used as defaults for any records where they\nare missing. This information can also be inferred from the token, assuming it\ndoesn't have permissions for multiple domains/hosts. This can make client code\nless verbose.\n\n`type` is the record type such as `A`, `CNAME`, `MX`, etc. `ttl` and\n`priority` are both integers.\n\nWhen setting `value` for a record, the template variable `{{host}}` can be\nused. It will be replaced with the actual host value when the server evaluates\nthe record. This is particularly useful for things like DKIM and ACME challenge\nrecords.\n\n\n## `POST /get-records`\n\nRetrieves current records. This could be done via DNS, but it's convenient to\nprovide it as part of the NameDrop API to make things easier for clients.\n\nThe request is JSON in the following format:\n\n```javascript\n{\n  \"domain\": String(),\n  \"host\": String(),\n  \"token\": String(),\n}\n```\n\nExample:\n\n```json\n{\n  \"domain\": \"example.com\",\n  \"host\": \"sub\",\n  \"token\": \"lkjaslkajsoidfnaiosnf\"\n}\n```\n\n## `POST /create-records`\n\nCreate new records, returning an error if any duplicate records exist.\n\n```javascript\n{\n  \"domain\": String(),\n  \"host\": String(),\n  \"records\": [\n  {\n    \"domain\": String(),\n    \"host\": String(),\n    \"type\": String(),\n    \"value\": String(),\n    \"ttl\": Number(),\n    \"priority\": Number(),\n  },\n  // more records\n}\n```\n\nExample:\n\n```json\n{\n  \"domain\": \"example.com\",\n  \"host\": \"sub\",\n  \"token\": \"lkjaslkajsoidfnaiosnf\",\n  \"records\": [\n    {\n      \"type\": \"A\",\n      \"value\": \"192.168.0.1\"\n    },\n    {\n      \"host\": \"sub1._domainkey.{{host}}\",\n      \"type\": \"CNAME\",\n      \"value\": \"f1.example.com.dkim-server.com\"\n    }\n  ]\n}\n```\n\n## `POST /set-records`\n\nSet records, overriding any existing duplicate records.\n\n```javascript\n{\n  \"domain\": String(),\n  \"host\": String(),\n  \"records\": [\n  {\n    \"domain\": String(),\n    \"host\": String(),\n    \"type\": String(),\n    \"value\": String(),\n    \"ttl\": Number(),\n    \"priority\": Number(),\n  },\n  // more records\n}\n```\n\nExample:\n\n```json\n{\n  \"domain\": \"example.com\",\n  \"host\": \"sub\",\n  \"token\": \"lkjaslkajsoidfnaiosnf\",\n  \"records\": [\n    {\n      \"type\": \"A\",\n      \"value\": \"192.168.0.1\"\n    },\n    {\n      \"host\": \"sub1._domainkey.{{host}}\",\n      \"type\": \"CNAME\",\n      \"value\": \"f1.example.com.dkim-server.com\"\n    }\n  ]\n}\n```\n\n## `POST /delete-records`\n\nDelete records, silently ignoring any records that don't exist.\n\n```javascript\n{\n  \"domain\": String(),\n  \"host\": String(),\n  \"records\": [\n  {\n    \"domain\": String(),\n    \"host\": String(),\n    \"type\": String(),\n    \"value\": String(),\n    \"ttl\": Number(),\n    \"priority\": Number(),\n  },\n  // more records\n}\n```\n\nExample:\n\n```json\n{\n  \"domain\": \"example.com\",\n  \"host\": \"sub\",\n  \"token\": \"lkjaslkajsoidfnaiosnf\",\n  \"records\": [\n    {\n      \"type\": \"A\",\n      \"value\": \"192.168.0.1\"\n    },\n    {\n      \"host\": \"sub1._domainkey.{{host}}\",\n      \"type\": \"CNAME\",\n      \"value\": \"f1.example.com.dkim-server.com\"\n    }\n  ]\n}\n```\n\n\n\n# Other endpoints\n\n## `GET /my-ip`\n\nReturns the public IP of the client, as observed from the server. This is\nuseful for helping self-hosted clients test whether they can be reached by the\noutside world.\n\nThe IP is returned as a simple string.\n\n\n## `GET /temp-subdomain`\n\nThis causes the server to create a special `A` or `AAAA` record pointing at\nthe client's IP address, as observed by the server. The created domain is\nreturned as a simple string.\n\nThe purpose of these domains is to allow the client to retrieve a TLS\ncertificate from a service like [LetsEncrypt][3], which makes the OAuth2 flows\nmore secure. This is particularly useful for self-hosters who are trying to\nbootstrap a service that doesn't yet have a domain or certificate.\n\nOne way for the server to create these records is by making the subdomain a\nrepresentation of the client's IP address.\n\nFor example, if TakingNames received a `/temp-subdomain` request from\n`157.245.231.242`, it would create the record and returns something like\nthis:\n\n`157-245-231-242.tkip.live`\n\nThe server should ensure the domain remains valid for at least 5 minutes\nafter a successful request, but no guarantees are required beyond that.\n\nServer implementors should be aware that free subdomains like this\nmay be abused for phishing attacks if the records are left around\nfor too long.\n\n\n\n[0]: https://takingnames.io\n\n[1]: https://oauth.net/2/\n\n[2]: https://aaronparecki.com/2018/07/07/7/oauth-for-the-open-web\n\n[3]: https://letsencrypt.org/\n","funding_links":[],"categories":["Protocols and Specifications"],"sub_categories":["Infrastructure"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftakingnames%2Fnamedrop-protocol-spec","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftakingnames%2Fnamedrop-protocol-spec","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftakingnames%2Fnamedrop-protocol-spec/lists"}