{"action":"opened","number":26,"pull_request":{"url":"https://api.github.com/repos/yunojuno/django-request-token/pulls/26","id":227473884,"node_id":"MDExOlB1bGxSZXF1ZXN0MjI3NDczODg0","html_url":"https://github.com/yunojuno/django-request-token/pull/26","diff_url":"https://github.com/yunojuno/django-request-token/pull/26.diff","patch_url":"https://github.com/yunojuno/django-request-token/pull/26.patch","issue_url":"https://api.github.com/repos/yunojuno/django-request-token/issues/26","number":26,"state":"open","locked":false,"title":"Add support for POST request handling","user":{"login":"hugorodgerbrown","id":200944,"node_id":"MDQ6VXNlcjIwMDk0NA==","avatar_url":"https://avatars3.githubusercontent.com/u/200944?v=4","gravatar_id":"","url":"https://api.github.com/users/hugorodgerbrown","html_url":"https://github.com/hugorodgerbrown","followers_url":"https://api.github.com/users/hugorodgerbrown/followers","following_url":"https://api.github.com/users/hugorodgerbrown/following{/other_user}","gists_url":"https://api.github.com/users/hugorodgerbrown/gists{/gist_id}","starred_url":"https://api.github.com/users/hugorodgerbrown/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/hugorodgerbrown/subscriptions","organizations_url":"https://api.github.com/users/hugorodgerbrown/orgs","repos_url":"https://api.github.com/users/hugorodgerbrown/repos","events_url":"https://api.github.com/users/hugorodgerbrown/events{/privacy}","received_events_url":"https://api.github.com/users/hugorodgerbrown/received_events","type":"User","site_admin":false},"body":"This PR adds basic (/experimental) support for processing POST requests, using the Django CSRF pattern as its model. It adds a template tag and a context processor to enable a request token that comes in on a GET request (the standard 'magic link' method) to be passed into a form POST, and then stripped back out, so that it can be used. This implementation requires no change to the existing GET handling.\r\n\r\nIn addition, a new model method `RequestToken.invalidate()` is available to immediately invalidate a token - this gives explicit control to a function, rather than relying on the token to expire at some point. This enables the scenario where magic links are not expired on the initial GET (which is prone to failure due to caching of URLs, poor connections etc.), but on a subsequent POST.\r\n\r\nPutting all of this together - the landing page for a link protected by a request_token querystring can add the request token to a form, which will then be handled by the middleware in the same way as the GET request (putting it on the request object).\r\n\r\n```\r\n<!-- template rendered on GET landing page -->\r\n<form method=\"post\">\r\n    {% csrf_token %}\r\n    {% request_token %}\r\n    ...\r\n</form>\r\n```\r\n\r\nThis will render the `token.jwt()` output in a hidden field:\r\n\r\n```html\r\n<form method=\"post\">\r\n    <input type=\"hidden\" name=\"csrfmiddlewaretoken\" value=\"12345678\">\r\n    <input type=\"hidden\" name=\"rt\" value=\"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJtYXgiOjEsInN1YiI6Im1hZ2ljX2xpbmtfbG9naW4iLCJtb2QiOiJzIiwianRpIjoxNzM5NSwiYXVkIjo5Mzc2NywiZXhwIjoxNMDI5MDcwLCJpYXQiOjE1NDEwMjg0NzB9.cpRoL0dKAB2XRLLNsV_F_vwIL39C2QqNAnwRgr6YoEE\">\r\n    ...\r\n</form>\r\n```\r\n\r\nAs with CSRF middleware, the `RequestTokenMiddleware` will automatically pick up the token, and add the token to the request object (as it would on a GET request). This means that you can decorate the request handling view with the `use_request_token` decorator, and use the `request.token` inside the function. If relevant, you can also now immediately invalidate the token should that be required.\r\n\r\n```python\r\n@use_request_token(scope='foo')\r\ndef process_form(request):\r\n    \"\"\"\"View handler for form presented on initial GET request.\"\"\"\r\n    token = request.token\r\n    do_something_with_token(token)\r\n    ...\r\n    # we don't want this token to be used again, so we immediately expire it\r\n    token.invalidate()\r\n    return HttpResponse('OK')\r\n```","created_at":"2018-10-31T23:41:09Z","updated_at":"2018-10-31T23:41:09Z","closed_at":null,"merged_at":null,"merge_commit_sha":null,"assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/yunojuno/django-request-token/pulls/26/commits","review_comments_url":"https://api.github.com/repos/yunojuno/django-request-token/pulls/26/comments","review_comment_url":"https://api.github.com/repos/yunojuno/django-request-token/pulls/comments{/number}","comments_url":"https://api.github.com/repos/yunojuno/django-request-token/issues/26/comments","statuses_url":"https://api.github.com/repos/yunojuno/django-request-token/statuses/6a9adb39764821125c3b50c1c52a11e005c72984","head":{"label":"yunojuno:issues/5","ref":"issues/5","sha":"6a9adb39764821125c3b50c1c52a11e005c72984","user":{"login":"yunojuno","id":1175789,"node_id":"MDEyOk9yZ2FuaXphdGlvbjExNzU3ODk=","avatar_url":"https://avatars2.githubusercontent.com/u/1175789?v=4","gravatar_id":"","url":"https://api.github.com/users/yunojuno","html_url":"https://github.com/yunojuno","followers_url":"https://api.github.com/users/yunojuno/followers","following_url":"https://api.github.com/users/yunojuno/following{/other_user}","gists_url":"https://api.github.com/users/yunojuno/gists{/gist_id}","starred_url":"https://api.github.com/users/yunojuno/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/yunojuno/subscriptions","organizations_url":"https://api.github.com/users/yunojuno/orgs","repos_url":"https://api.github.com/users/yunojuno/repos","events_url":"https://api.github.com/users/yunojuno/events{/privacy}","received_events_url":"https://api.github.com/users/yunojuno/received_events","type":"Organization","site_admin":false},"repo":{"id":48643803,"node_id":"MDEwOlJlcG9zaXRvcnk0ODY0MzgwMw==","name":"django-request-token","full_name":"yunojuno/django-request-token","private":false,"owner":{"login":"yunojuno","id":1175789,"node_id":"MDEyOk9yZ2FuaXphdGlvbjExNzU3ODk=","avatar_url":"https://avatars2.githubusercontent.com/u/1175789?v=4","gravatar_id":"","url":"https://api.github.com/users/yunojuno","html_url":"https://github.com/yunojuno","followers_url":"https://api.github.com/users/yunojuno/followers","following_url":"https://api.github.com/users/yunojuno/following{/other_user}","gists_url":"https://api.github.com/users/yunojuno/gists{/gist_id}","starred_url":"https://api.github.com/users/yunojuno/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/yunojuno/subscriptions","organizations_url":"https://api.github.com/users/yunojuno/orgs","repos_url":"https://api.github.com/users/yunojuno/repos","events_url":"https://api.github.com/users/yunojuno/events{/privacy}","received_events_url":"https://api.github.com/users/yunojuno/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/yunojuno/django-request-token","description":"Django app that uses JWT to manage one-time and expiring tokens to protected URLs.","fork":false,"url":"https://api.github.com/repos/yunojuno/django-request-token","forks_url":"https://api.github.com/repos/yunojuno/django-request-token/forks","keys_url":"https://api.github.com/repos/yunojuno/django-request-token/keys{/key_id}","collaborators_url":"https://api.github.com/repos/yunojuno/django-request-token/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/yunojuno/django-request-token/teams","hooks_url":"https://api.github.com/repos/yunojuno/django-request-token/hooks","issue_events_url":"https://api.github.com/repos/yunojuno/django-request-token/issues/events{/number}","events_url":"https://api.github.com/repos/yunojuno/django-request-token/events","assignees_url":"https://api.github.com/repos/yunojuno/django-request-token/assignees{/user}","branches_url":"https://api.github.com/repos/yunojuno/django-request-token/branches{/branch}","tags_url":"https://api.github.com/repos/yunojuno/django-request-token/tags","blobs_url":"https://api.github.com/repos/yunojuno/django-request-token/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/yunojuno/django-request-token/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/yunojuno/django-request-token/git/refs{/sha}","trees_url":"https://api.github.com/repos/yunojuno/django-request-token/git/trees{/sha}","statuses_url":"https://api.github.com/repos/yunojuno/django-request-token/statuses/{sha}","languages_url":"https://api.github.com/repos/yunojuno/django-request-token/languages","stargazers_url":"https://api.github.com/repos/yunojuno/django-request-token/stargazers","contributors_url":"https://api.github.com/repos/yunojuno/django-request-token/contributors","subscribers_url":"https://api.github.com/repos/yunojuno/django-request-token/subscribers","subscription_url":"https://api.github.com/repos/yunojuno/django-request-token/subscription","commits_url":"https://api.github.com/repos/yunojuno/django-request-token/commits{/sha}","git_commits_url":"https://api.github.com/repos/yunojuno/django-request-token/git/commits{/sha}","comments_url":"https://api.github.com/repos/yunojuno/django-request-token/comments{/number}","issue_comment_url":"https://api.github.com/repos/yunojuno/django-request-token/issues/comments{/number}","contents_url":"https://api.github.com/repos/yunojuno/django-request-token/contents/{+path}","compare_url":"https://api.github.com/repos/yunojuno/django-request-token/compare/{base}...{head}","merges_url":"https://api.github.com/repos/yunojuno/django-request-token/merges","archive_url":"https://api.github.com/repos/yunojuno/django-request-token/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/yunojuno/django-request-token/downloads","issues_url":"https://api.github.com/repos/yunojuno/django-request-token/issues{/number}","pulls_url":"https://api.github.com/repos/yunojuno/django-request-token/pulls{/number}","milestones_url":"https://api.github.com/repos/yunojuno/django-request-token/milestones{/number}","notifications_url":"https://api.github.com/repos/yunojuno/django-request-token/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/yunojuno/django-request-token/labels{/name}","releases_url":"https://api.github.com/repos/yunojuno/django-request-token/releases{/id}","deployments_url":"https://api.github.com/repos/yunojuno/django-request-token/deployments","created_at":"2015-12-27T12:15:19Z","updated_at":"2018-10-26T12:17:02Z","pushed_at":"2018-10-31T23:14:11Z","git_url":"git://github.com/yunojuno/django-request-token.git","ssh_url":"git@github.com:yunojuno/django-request-token.git","clone_url":"https://github.com/yunojuno/django-request-token.git","svn_url":"https://github.com/yunojuno/django-request-token","homepage":"","size":153,"stargazers_count":21,"watchers_count":21,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":14,"mirror_url":null,"archived":false,"open_issues_count":7,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":14,"open_issues":7,"watchers":21,"default_branch":"master"}},"base":{"label":"yunojuno:master","ref":"master","sha":"efe47f75960d57dba8c6d180560f92aa35115ca1","user":{"login":"yunojuno","id":1175789,"node_id":"MDEyOk9yZ2FuaXphdGlvbjExNzU3ODk=","avatar_url":"https://avatars2.githubusercontent.com/u/1175789?v=4","gravatar_id":"","url":"https://api.github.com/users/yunojuno","html_url":"https://github.com/yunojuno","followers_url":"https://api.github.com/users/yunojuno/followers","following_url":"https://api.github.com/users/yunojuno/following{/other_user}","gists_url":"https://api.github.com/users/yunojuno/gists{/gist_id}","starred_url":"https://api.github.com/users/yunojuno/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/yunojuno/subscriptions","organizations_url":"https://api.github.com/users/yunojuno/orgs","repos_url":"https://api.github.com/users/yunojuno/repos","events_url":"https://api.github.com/users/yunojuno/events{/privacy}","received_events_url":"https://api.github.com/users/yunojuno/received_events","type":"Organization","site_admin":false},"repo":{"id":48643803,"node_id":"MDEwOlJlcG9zaXRvcnk0ODY0MzgwMw==","name":"django-request-token","full_name":"yunojuno/django-request-token","private":false,"owner":{"login":"yunojuno","id":1175789,"node_id":"MDEyOk9yZ2FuaXphdGlvbjExNzU3ODk=","avatar_url":"https://avatars2.githubusercontent.com/u/1175789?v=4","gravatar_id":"","url":"https://api.github.com/users/yunojuno","html_url":"https://github.com/yunojuno","followers_url":"https://api.github.com/users/yunojuno/followers","following_url":"https://api.github.com/users/yunojuno/following{/other_user}","gists_url":"https://api.github.com/users/yunojuno/gists{/gist_id}","starred_url":"https://api.github.com/users/yunojuno/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/yunojuno/subscriptions","organizations_url":"https://api.github.com/users/yunojuno/orgs","repos_url":"https://api.github.com/users/yunojuno/repos","events_url":"https://api.github.com/users/yunojuno/events{/privacy}","received_events_url":"https://api.github.com/users/yunojuno/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/yunojuno/django-request-token","description":"Django app that uses JWT to manage one-time and expiring tokens to protected URLs.","fork":false,"url":"https://api.github.com/repos/yunojuno/django-request-token","forks_url":"https://api.github.com/repos/yunojuno/django-request-token/forks","keys_url":"https://api.github.com/repos/yunojuno/django-request-token/keys{/key_id}","collaborators_url":"https://api.github.com/repos/yunojuno/django-request-token/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/yunojuno/django-request-token/teams","hooks_url":"https://api.github.com/repos/yunojuno/django-request-token/hooks","issue_events_url":"https://api.github.com/repos/yunojuno/django-request-token/issues/events{/number}","events_url":"https://api.github.com/repos/yunojuno/django-request-token/events","assignees_url":"https://api.github.com/repos/yunojuno/django-request-token/assignees{/user}","branches_url":"https://api.github.com/repos/yunojuno/django-request-token/branches{/branch}","tags_url":"https://api.github.com/repos/yunojuno/django-request-token/tags","blobs_url":"https://api.github.com/repos/yunojuno/django-request-token/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/yunojuno/django-request-token/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/yunojuno/django-request-token/git/refs{/sha}","trees_url":"https://api.github.com/repos/yunojuno/django-request-token/git/trees{/sha}","statuses_url":"https://api.github.com/repos/yunojuno/django-request-token/statuses/{sha}","languages_url":"https://api.github.com/repos/yunojuno/django-request-token/languages","stargazers_url":"https://api.github.com/repos/yunojuno/django-request-token/stargazers","contributors_url":"https://api.github.com/repos/yunojuno/django-request-token/contributors","subscribers_url":"https://api.github.com/repos/yunojuno/django-request-token/subscribers","subscription_url":"https://api.github.com/repos/yunojuno/django-request-token/subscription","commits_url":"https://api.github.com/repos/yunojuno/django-request-token/commits{/sha}","git_commits_url":"https://api.github.com/repos/yunojuno/django-request-token/git/commits{/sha}","comments_url":"https://api.github.com/repos/yunojuno/django-request-token/comments{/number}","issue_comment_url":"https://api.github.com/repos/yunojuno/django-request-token/issues/comments{/number}","contents_url":"https://api.github.com/repos/yunojuno/django-request-token/contents/{+path}","compare_url":"https://api.github.com/repos/yunojuno/django-request-token/compare/{base}...{head}","merges_url":"https://api.github.com/repos/yunojuno/django-request-token/merges","archive_url":"https://api.github.com/repos/yunojuno/django-request-token/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/yunojuno/django-request-token/downloads","issues_url":"https://api.github.com/repos/yunojuno/django-request-token/issues{/number}","pulls_url":"https://api.github.com/repos/yunojuno/django-request-token/pulls{/number}","milestones_url":"https://api.github.com/repos/yunojuno/django-request-token/milestones{/number}","notifications_url":"https://api.github.com/repos/yunojuno/django-request-token/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/yunojuno/django-request-token/labels{/name}","releases_url":"https://api.github.com/repos/yunojuno/django-request-token/releases{/id}","deployments_url":"https://api.github.com/repos/yunojuno/django-request-token/deployments","created_at":"2015-12-27T12:15:19Z","updated_at":"2018-10-26T12:17:02Z","pushed_at":"2018-10-31T23:14:11Z","git_url":"git://github.com/yunojuno/django-request-token.git","ssh_url":"git@github.com:yunojuno/django-request-token.git","clone_url":"https://github.com/yunojuno/django-request-token.git","svn_url":"https://github.com/yunojuno/django-request-token","homepage":"","size":153,"stargazers_count":21,"watchers_count":21,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":14,"mirror_url":null,"archived":false,"open_issues_count":7,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":14,"open_issues":7,"watchers":21,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/yunojuno/django-request-token/pulls/26"},"html":{"href":"https://github.com/yunojuno/django-request-token/pull/26"},"issue":{"href":"https://api.github.com/repos/yunojuno/django-request-token/issues/26"},"comments":{"href":"https://api.github.com/repos/yunojuno/django-request-token/issues/26/comments"},"review_comments":{"href":"https://api.github.com/repos/yunojuno/django-request-token/pulls/26/comments"},"review_comment":{"href":"https://api.github.com/repos/yunojuno/django-request-token/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/yunojuno/django-request-token/pulls/26/commits"},"statuses":{"href":"https://api.github.com/repos/yunojuno/django-request-token/statuses/6a9adb39764821125c3b50c1c52a11e005c72984"}},"author_association":"MEMBER","merged":false,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":null,"comments":0,"review_comments":0,"maintainer_can_modify":false,"commits":2,"additions":271,"deletions":31,"changed_files":14}}
{"actor":{"display_login":"hugorodgerbrown"}}