A link's creator should be able to delete it, and nobody else. Codes count 1, 2, 3, so anyone can guess them: knowing a code proves nothing. The proof is a key, a random secret the app makes with each new link and sends only to its creator, in the 201 answer. To delete, the client sends that key back in a request header named X-Owner-Key.
Java's SecureRandom makes numbers nobody can guess, HexFormat writes them as hexadecimal text, and a map's remove takes out a key and its value:
String token = HexFormat.of().toHexDigits(new SecureRandom().nextLong()); // 16 hex characters, like "4f0c9a7e21b3d865"
Map<String, Integer> scores = new HashMap<>(Map.of("ann", 3, "bob", 5));
scores.remove("ann"); // scores is now {bob=5}
A method reads a header with a @RequestHeader parameter. With required = false, the parameter is null when the header is missing. Here is a header check on another route:
@GetMapping("/admin/report")
Object adminReport(@RequestHeader(value = "X-Admin-Token", required = false) String token) {
if (!ADMIN_TOKEN.equals(token)) {
return ResponseEntity.status(403).body(Map.of("error", "Not allowed"));
}
return Map.of("links", links.size());
}
403 Forbidden means "you may not do this". A missing key and a wrong key get the same 403: there are no accounts to log in to. A delete that works answers 204 No Content, with an empty body. These are the test requests, in order (<key>, <key2> and <key3> stand for the random keys of links 1, 2 and 3):
POST /links {"url": "https://www.python.org/downloads/"} → 201 {"code": "1", "url": "https://www.python.org/downloads/", "key": "<key>"}
POST /links {"url": "https://en.wikipedia.org/wiki/URL_shortening"} → 201 {"code": "2", "url": "https://en.wikipedia.org/wiki/URL_shortening", "key": "<key2>"}
POST /links {"url": "https://www.python.org/downloads/"} → 200 {"code": "1", "url": "https://www.python.org/downloads/"}
DELETE /links/1 (no key) → 403 {"error": "Wrong or missing key"}
DELETE /links/1 X-Owner-Key: <key2> → 403 {"error": "Wrong or missing key"}
DELETE /links/1 X-Owner-Key: <key> → 204 (empty body)
GET /1 → 404 {"error": "Link not found"}
DELETE /links/1 X-Owner-Key: <key> → 404 {"error": "Link not found"}
GET /links/2/stats → 200 {"code": "2", "url": "https://en.wikipedia.org/wiki/URL_shortening", "clicks": 0}
POST /links {"url": "https://flask.palletsprojects.com/en/stable/quickstart/"} → 201 {"code": "3", "url": "https://flask.palletsprojects.com/en/stable/quickstart/", "key": "<key3>"}
Your task: in createLink, make a key with SecureRandom and HexFormat, store it with the new link and add it to the 201 answer. Then add DELETE /links/{code} to LinkController: 404 for an unknown code, 403 when the X-Owner-Key header isn't that link's key, and otherwise remove the link and answer an empty 204.
Assignment
Follow the Coding Tutorial to give every new link a secret key and to add the DELETE route that checks it.
Hint
Check the code before the key: for an unknown code, links.get(code) is null, and calling .get("key") on it would crash. The answer for a URL that was already shortened stays without the key. ResponseEntity.noContent() starts an empty 204 answer.
Free plan: 30 tutor messages. PRO is unlimited.
