Reinitalize repository

This commit is contained in:
Erisa A 2021-03-18 19:52:05 +00:00
commit 44aac0fe67
No known key found for this signature in database
GPG key ID: D581704D7FC37F30
8 changed files with 369 additions and 0 deletions

1
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1 @@
github: Erisa

11
.gitignore vendored Normal file
View file

@ -0,0 +1,11 @@
/target
/dist
**/*.rs.bk
Cargo.lock
bin/
pkg/
wasm-pack.log
worker/
node_modules/
.cargo-ok
wrangler.prod.toml

7
.prettierrc Normal file
View file

@ -0,0 +1,7 @@
{
"singleQuote": true,
"semi": false,
"trailingComma": "all",
"tabWidth": 2,
"printWidth": 80
}

25
LICENSE Normal file
View file

@ -0,0 +1,25 @@
Copyright (c) 2021 Erisa A.
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

78
README.md Normal file
View file

@ -0,0 +1,78 @@
# Worker Links (URL Shortener)
A simple URL Shortener for Cloudflare Workers, using Workers KV. Redirect short URLs at the edge of the Cloudflare network to keep latency down and access fast!
I run this code in production on [erisa.link](https://erisa.link/example), though the root name redirects and without the secret it doesn't make a very good demo.
It was made for my personal use but is available publicly in the hopes that it may be useful to someone somewhere.
## Usage
To deploy to your Cloudflare Workers account, edit the relevant entries in `wrangler.toml`, add a secret with `wrangler put WORKERLINKS_SECRET` and use `wrangler publish`.
For debugging, you can use `wrangler preview`, though note you will need to login and configure a preview KV namespace in `wrangler.toml`.
Once deployed, interacting with the API should be rather simple. It's based on headers, specifically with the `Authorization` and `URL` headers.
To create a short URL with a random URL, send a `POST` to `/` with `Authorization` and `URL` headers:
```json
erisa@Tuturu:~$ curl -X POST -H "Authorization: mysecret" -H "URL: https://erisa.moe" https://erisa.link/
{
"message": "URL created succesfully.",
"key": "q2w083eq",
"shorturl": "https://erisa.link/q2w083eq",
"longurl": "https://erisa.moe"
}
```
And you can test it worked if you wish:
```http
erisa@Tuturu:~$ curl https://erisa.link/q2w083eq -D-
HTTP/2 302
date: Fri, 11 Sep 2020 12:43:04 GMT
content-length: 0
location: https://erisa.moe
server: cloudflare
..other ephemeral headers..
```
To create or update a custom short URl, send a `PUT` to the intended target URL:
```json
erisa@Tuturu:~$ curl -X PUT -H "Authorization: mysecret" -H "URL: https://erisa.moe" https://erisa.link/mywebsite
{
"message": "URL created succesfully.",
"key": "mywebsite",
"shorturl": "https://erisa.link/mywebsite",
"longurl": "https://erisa.moe"
}
```
And to delete an existing shortlink, send a `DELETE` to it with only the `Authorization` header:
```json
erisa@Tuturu:~$ curl -X DELETE -H "Authorization: mysecret" https://erisa.link/keytodelete
{
"message": "Short URL deleted succesfully.",
"key": "keytodelete",
"shorturl": "https://erisa.link/keytodelete",
"longurl": "https://erisa.moe"
}
```
It is a planned feature to be able to list all URLs via a `GET` on `/` with `Authorization`.
For the time being you can view them from your Cloudflare Dashboard:
Cloudflare Dashboard -> Workers -> KV -> View on the namespace.
Or with the `wrangler` tool.
For example if you are in the project directory and have your `wrangler.toml` configured correctly, this should just be `wrangler kv:key list --binding kv`
## Security
This code is relatively simple but still, if you find any security issues that can be exploited publicly, please reach out to me via email: `seriel (at) erisa.moe` with any relevant details.
If you don't have access to Workers KV you're welcome to test these issues on my live `erisa.link`, provided you don't send excessive (constant) requests or delete/modify any keys except ones created by you or the `/sample` key.
If I don't respond to your email for whatever reason please feel free to publicly open an issue.

220
index.js Normal file
View file

@ -0,0 +1,220 @@
// Set this in your worker's environment. wrangler.toml or cloudflare dashboard.
let secret = WORKERLINKS_SECRET
addEventListener('fetch', (event) => {
event.respondWith(handleRequest(event.request))
})
/**
* Respond to the request
* @param {Request} request
*/
async function handleRequest(request) {
var key = new URL(request.url).pathname
var shorturl = new URL(request.url).origin + key
if (request.method == 'PUT') {
return await putLink(
request.headers.get('Authorization'),
shorturl,
key,
request.headers.get('URL'),
)
} else if (request.method == 'POST') {
if (key != '/') {
return new Response(
JSON.stringify({
code: '405 Method Not Allowed',
message: 'POST not valid for individual keys. Did you mean PUT?',
}),
{
status: 405,
headers: {
'Content-Type': 'application/json',
},
},
)
}
key = '/' + Math.random().toString(36).slice(5)
shorturl = new URL(request.url).origin + key
return await putLink(
request.headers.get('Authorization'),
shorturl,
key,
request.headers.get('URL'),
)
} else if (request.method == 'GET' || request.method == 'HEAD') {
let url = await kv.get(key)
if (url == null) {
return new Response(
JSON.stringify(
{
code: '404 Not Found',
message: 'Key does not exist or has not propagated.',
},
null,
2,
),
{
status: 404,
headers: {
'Content-Type': 'application/json',
},
},
)
} else {
return new Response(null, { status: 302, headers: { Location: url } })
}
} else if (request.method == 'DELETE') {
if (request.headers.get('Authorization') != secret) {
return new Response(
JSON.stringify(
{
code: '401 Unauthorized',
message: 'Unauthorized.',
},
null,
2,
),
{
status: 401,
headers: {
'Content-Type': 'application/json',
},
},
)
}
shorturl = new URL(request.url).origin + key
let url = await kv.get(key)
if (url == null) {
return new Response(
JSON.stringify(
{
code: '404 Not Found',
message: 'Key does not exist or has not propagated.',
},
null,
2,
),
{
status: 404,
headers: {
'Content-Type': 'application/json',
},
},
)
} else {
kv.delete(key)
return new Response(
JSON.stringify(
{
message: 'Short URL deleted succesfully.',
key: key.substr(1),
shorturl: shorturl,
longurl: url,
},
null,
2,
),
{
status: 200,
headers: {
'Content-Type': 'application/json',
},
},
)
}
}
return new Response(
JSON.stringify(
{
code: '405 Method Not Allowed',
message:
'Unsupported method. Please use one of GET, PUT, POST, DELETE, HEAD.',
},
null,
2,
),
{
status: 405,
headers: {
'Content-Type': 'application/json',
},
},
)
}
function validateUrl(url) {
// quick and dirty validation
if (url == '') {
return false
}
try {
new URL(url)
} catch (TypeError) {
return false
}
return true
}
async function putLink(givenSecret, shorturl, key, url) {
if (givenSecret != secret) {
return new Response(
JSON.stringify(
{
code: '401 Unauthorized',
message: 'Unauthorized.',
},
null,
2,
),
{
status: 401,
headers: {
'Content-Type': 'application/json',
},
},
)
}
if (url == null || !validateUrl(url)) {
return new Response(
JSON.stringify(
{
code: '400 Bad Request',
message: "No valid URL given. Please set a 'URL' header.",
},
null,
2,
),
{
status: 400,
headers: {
'Content-Type': 'application/json',
},
},
)
}
await kv.put(key, url)
return new Response(
JSON.stringify(
{
message: 'URL created succesfully.',
key: key.substr(1),
shorturl: shorturl,
longurl: url,
},
null,
2,
),
{
status: 200,
headers: {
'Content-Type': 'application/json',
},
},
)
}

16
package.json Normal file
View file

@ -0,0 +1,16 @@
{
"private": true,
"name": "worker-links",
"version": "1.0.0",
"description": "Simple link shortener for Cloudflare Workers.",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"format": "prettier --write '**/*.{js,css,json,md}'"
},
"author": "Erisa A",
"license": "MIT",
"devDependencies": {
"prettier": "^1.18.2"
}
}

11
wrangler.toml Normal file
View file

@ -0,0 +1,11 @@
name = "worker-links"
type = "javascript"
# Change these!!
account_id = "ece1d09b06af2ced51407c97505ea0cc"
zone_id = "e2eabc0f0319355c842fd443ad480ade"
kv_namespaces = [ { binding = "kv", id = "1be44406edc142a084435e24dbf8ae1d", preview_id = "15cfb90ecd654b8f8a9ccd600832093f" }]
# Remove or comment out the route line if using workers_dev
workers_dev = false
route = "erisa.link/*"