# Reproduce the request Recustomer sends, against your local endpoint
curl -X GET 'http://localhost:3000/products/PRODUCT_ID' \
-H 'api_token: X12345678'
// npm install express
import crypto from 'node:crypto';
import express from 'express';
const token = process.env.RECUSTOMER_API_TOKEN;
if (!token) throw new Error('RECUSTOMER_API_TOKEN is not set');
// Compare in constant time so the response time does not reveal the expected value
const safeEqual = (a, b) => {
const x = Buffer.from(a);
const y = Buffer.from(b);
return x.length === y.length && crypto.timingSafeEqual(x, y);
};
const app = express();
app.use(express.json());
app.get("/products/:product_id", (req, res) => {
if (!safeEqual(req.get("api_token") ?? '', token)) {
return res.status(401).json({ message: 'Unauthorized' });
}
const productId = req.params["product_id"];
console.log(productId);
res.status(200).json({
"product_id": "product_id",
"product_name": "テスト商品",
"image_url": "https://recustomer.me/image/12345",
"created_at": "2023-05-10T12:00:00+09:00",
"updated_at": "2023-05-12T10:30:00+09:00",
"variants": [
{
"variant_id": "2538974832789",
"sku": "test-product",
"image_url": "https://recustomer.me/image/12345",
"name": "テスト商品",
"quantity_in_stock": 0,
"created_at": "2023-05-10T12:00:00+09:00",
"updated_at": "2023-05-12T10:30:00+09:00",
"variant_options": [
{
"name": "カラー",
"value": "レッド"
}
]
}
]
});
});
app.listen(3000);
# pip install flask
import hmac
import os
from flask import Flask, jsonify, request
TOKEN = os.environ.get("RECUSTOMER_API_TOKEN", "")
if not TOKEN:
raise RuntimeError("RECUSTOMER_API_TOKEN is not set")
app = Flask(__name__)
@app.route("/products/<product_id>", methods=["GET"])
def handle(product_id):
received = request.headers.get("api_token", "")
if not hmac.compare_digest(received.encode(), TOKEN.encode()):
return jsonify({"message": "Unauthorized"}), 401
print(product_id)
return jsonify({
"product_id": "product_id",
"product_name": "テスト商品",
"image_url": "https://recustomer.me/image/12345",
"created_at": "2023-05-10T12:00:00+09:00",
"updated_at": "2023-05-12T10:30:00+09:00",
"variants": [
{
"variant_id": "2538974832789",
"sku": "test-product",
"image_url": "https://recustomer.me/image/12345",
"name": "テスト商品",
"quantity_in_stock": 0,
"created_at": "2023-05-10T12:00:00+09:00",
"updated_at": "2023-05-12T10:30:00+09:00",
"variant_options": [
{
"name": "カラー",
"value": "レッド",
},
],
},
],
}), 200
if __name__ == "__main__":
app.run(port=3000)
<?php
// Run: php -S localhost:3000 handler.php (GET /products/{product_id})
$token = getenv('RECUSTOMER_API_TOKEN');
if ($token === false || $token === '') {
http_response_code(500);
exit('RECUSTOMER_API_TOKEN is not set');
}
$headers = [];
foreach (getallheaders() as $name => $value) {
$headers[str_replace('_', '-', strtolower($name))] = $value;
}
if (!hash_equals($token, $headers['api-token'] ?? '')) {
http_response_code(401);
header('Content-Type: application/json');
echo '{"message":"Unauthorized"}';
exit;
}
preg_match('#^/products/([^/]+)$#', parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH), $matches);
$product_id = $matches[1] ?? null;
http_response_code(200);
header('Content-Type: application/json');
echo <<<'JSON'
{
"product_id": "product_id",
"product_name": "テスト商品",
"image_url": "https://recustomer.me/image/12345",
"created_at": "2023-05-10T12:00:00+09:00",
"updated_at": "2023-05-12T10:30:00+09:00",
"variants": [
{
"variant_id": "2538974832789",
"sku": "test-product",
"image_url": "https://recustomer.me/image/12345",
"name": "テスト商品",
"quantity_in_stock": 0,
"created_at": "2023-05-10T12:00:00+09:00",
"updated_at": "2023-05-12T10:30:00+09:00",
"variant_options": [
{
"name": "カラー",
"value": "レッド"
}
]
}
]
}
JSON;
package main
import (
"crypto/subtle"
"log"
"net/http"
"os"
)
const responseBody = `{
"product_id": "product_id",
"product_name": "テスト商品",
"image_url": "https://recustomer.me/image/12345",
"created_at": "2023-05-10T12:00:00+09:00",
"updated_at": "2023-05-12T10:30:00+09:00",
"variants": [
{
"variant_id": "2538974832789",
"sku": "test-product",
"image_url": "https://recustomer.me/image/12345",
"name": "テスト商品",
"quantity_in_stock": 0,
"created_at": "2023-05-10T12:00:00+09:00",
"updated_at": "2023-05-12T10:30:00+09:00",
"variant_options": [
{
"name": "カラー",
"value": "レッド"
}
]
}
]
}`
func main() {
token := os.Getenv("RECUSTOMER_API_TOKEN")
if token == "" {
log.Fatal("RECUSTOMER_API_TOKEN is not set")
}
http.HandleFunc("GET /products/{product_id}", func(w http.ResponseWriter, r *http.Request) {
if subtle.ConstantTimeCompare([]byte(r.Header.Get("api_token")), []byte(token)) != 1 {
http.Error(w, `{"message":"Unauthorized"}`, http.StatusUnauthorized)
return
}
log.Println(r.PathValue("product_id"))
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(200)
w.Write([]byte(responseBody))
})
http.ListenAndServe(":3000", nil)
}
// Cargo.toml: axum = "0.8", tokio = { version = "1", features = ["full"] }, subtle = "2"
use axum::{
extract::{Path, State},
http::{header, HeaderMap, StatusCode},
response::{IntoResponse, Response},
routing::get,
Router,
};
use subtle::ConstantTimeEq;
const RESPONSE_BODY: &str = r#"{
"product_id": "product_id",
"product_name": "テスト商品",
"image_url": "https://recustomer.me/image/12345",
"created_at": "2023-05-10T12:00:00+09:00",
"updated_at": "2023-05-12T10:30:00+09:00",
"variants": [
{
"variant_id": "2538974832789",
"sku": "test-product",
"image_url": "https://recustomer.me/image/12345",
"name": "テスト商品",
"quantity_in_stock": 0,
"created_at": "2023-05-10T12:00:00+09:00",
"updated_at": "2023-05-12T10:30:00+09:00",
"variant_options": [
{
"name": "カラー",
"value": "レッド"
}
]
}
]
}"#;
async fn handle(State(token): State<String>, Path(product_id): Path<String>, headers: HeaderMap) -> Response {
let received = headers.get("api_token").map(|v| v.as_bytes()).unwrap_or_default();
// Compare in constant time so the response time does not reveal the token
if !bool::from(received.ct_eq(token.as_bytes())) {
return StatusCode::UNAUTHORIZED.into_response();
}
println!("{product_id}");
(StatusCode::OK, [(header::CONTENT_TYPE, "application/json")], RESPONSE_BODY).into_response()
}
#[tokio::main]
async fn main() {
let state = std::env::var("RECUSTOMER_API_TOKEN")
.ok()
.filter(|v| !v.is_empty())
.expect("RECUSTOMER_API_TOKEN is not set");
let app = Router::new().route("/products/{product_id}", get(handle)).with_state(state);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}