Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion config.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"id": "paylink",
"version": "1.3.0",
"version": "1.3.1",
"name": "Pay Links",
"repo": "https://github.com/lnbits/lnurlp",
"short_description": "Make static reusable LNURL pay links or lightning addresses",
Expand Down
174 changes: 108 additions & 66 deletions views_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,54 @@ def check_lnurl_encode(req: Request, link: PayLink) -> str:
) from exc


def validate_paylink(data: CreatePayLinkData):
if data.min > data.max:
raise HTTPException(
detail="Min is greater than max.", status_code=HTTPStatus.BAD_REQUEST
)

if not data.currency:
if round(data.min) != data.min or round(data.max) != data.max or data.min < 1:
raise HTTPException(
detail="Must use full satoshis.", status_code=HTTPStatus.BAD_REQUEST
)

if data.webhook_headers:
try:
json.loads(data.webhook_headers)
except ValueError as exc:
raise HTTPException(
detail="Invalid JSON in webhook_headers.",
status_code=HTTPStatus.BAD_REQUEST,
) from exc

if data.webhook_body:
try:
json.loads(data.webhook_body)
except ValueError as exc:
raise HTTPException(
detail="Invalid JSON in webhook_body.",
status_code=HTTPStatus.BAD_REQUEST,
) from exc

if data.username and not re.match("^[a-z0-9-_.]{1,210}$", data.username):
raise HTTPException(
detail=f"Invalid username: {data.username}. "
"Only letters a-z0-9-_. allowed, min 1 and max 210 characters!",
status_code=HTTPStatus.BAD_REQUEST,
)

if (
data.success_url
and data.success_url != ""
and not data.success_url.startswith("https://")
):
raise HTTPException(
detail="Success URL must be secure https://...",
status_code=HTTPStatus.BAD_REQUEST,
)


@lnurlp_api_router.get("/api/v1/links", status_code=HTTPStatus.OK)
async def api_links(
req: Request,
Expand Down Expand Up @@ -110,65 +158,20 @@ async def check_username_exists(username: str):


@lnurlp_api_router.post("/api/v1/links", status_code=HTTPStatus.CREATED)
@lnurlp_api_router.put("/api/v1/links/{link_id}", status_code=HTTPStatus.OK)
async def api_link_create_or_update(
async def api_link_create(
req: Request,
data: CreatePayLinkData,
link_id: str | None = None,
key_info: WalletTypeInfo = Depends(require_admin_key),
) -> PayLink:
if data.min > data.max:
raise HTTPException(
detail="Min is greater than max.", status_code=HTTPStatus.BAD_REQUEST
)

if not data.currency:
if round(data.min) != data.min or round(data.max) != data.max or data.min < 1:
raise HTTPException(
detail="Must use full satoshis.", status_code=HTTPStatus.BAD_REQUEST
)

if data.webhook_headers:
try:
json.loads(data.webhook_headers)
except ValueError as exc:
raise HTTPException(
detail="Invalid JSON in webhook_headers.",
status_code=HTTPStatus.BAD_REQUEST,
) from exc

if data.webhook_body:
try:
json.loads(data.webhook_body)
except ValueError as exc:
raise HTTPException(
detail="Invalid JSON in webhook_body.",
status_code=HTTPStatus.BAD_REQUEST,
) from exc
validate_paylink(data)

# database only allows int4 entries for min and max. For fiat currencies,
# we multiply by data.fiat_base_multiplier (usually 100) to save the value in cents.
if data.currency and data.fiat_base_multiplier:
data.min *= data.fiat_base_multiplier
data.max *= data.fiat_base_multiplier

if (
data.success_url
and data.success_url != ""
and not data.success_url.startswith("https://")
):
raise HTTPException(
detail="Success URL must be secure https://...",
status_code=HTTPStatus.BAD_REQUEST,
)

if data.username and not re.match("^[a-z0-9-_.]{1,210}$", data.username):
raise HTTPException(
detail=f"Invalid username: {data.username}. "
"Only letters a-z0-9-_. allowed, min 1 and max 210 characters!",
status_code=HTTPStatus.BAD_REQUEST,
)

# if wallet is not provided, use the wallet of the key
if not data.wallet:
data.wallet = key_info.wallet.id
Expand All @@ -178,36 +181,75 @@ async def api_link_create_or_update(
raise HTTPException(
detail="Wallet does not exist.", status_code=HTTPStatus.FORBIDDEN
)
user = await get_user(key_info.wallet.user)
if not user:
raise HTTPException(
detail="User does not exist.", status_code=HTTPStatus.FORBIDDEN
)

if not user.admin and new_wallet.id not in user.wallet_ids:
raise HTTPException(detail="Not your wallet.", status_code=HTTPStatus.FORBIDDEN)

if data.username:
await check_username_exists(data.username)

link = await create_pay_link(data)
link.lnurl = check_lnurl_encode(req, link)
return link


@lnurlp_api_router.put("/api/v1/links/{link_id}")
async def api_link_update(
req: Request,
data: CreatePayLinkData,
link_id: str,
key_info: WalletTypeInfo = Depends(require_admin_key),
) -> PayLink:

validate_paylink(data)

link = await get_pay_link(link_id)
if not link:
raise HTTPException(
detail="Pay link does not exist.", status_code=HTTPStatus.NOT_FOUND
)

# admins are allowed to create/edit paylinks belonging to regular users
user = await get_user(key_info.wallet.user)
admin_user = user.admin if user else False
if not admin_user and new_wallet.user != key_info.wallet.user:
if not user:
raise HTTPException(
detail="Not your pay link.", status_code=HTTPStatus.FORBIDDEN
detail="User does not exist.", status_code=HTTPStatus.FORBIDDEN
)

if link_id:
link = await get_pay_link(link_id)
if not user.admin and link.wallet not in user.wallet_ids:
raise HTTPException(
detail="Not your pay link.", status_code=HTTPStatus.FORBIDDEN
)

if not link:
raise HTTPException(
detail="Pay link does not exist.", status_code=HTTPStatus.NOT_FOUND
)
# database only allows int4 entries for min and max. For fiat currencies,
# we multiply by data.fiat_base_multiplier (usually 100) to save the value in cents.
if data.currency and data.fiat_base_multiplier:
data.min *= data.fiat_base_multiplier
data.max *= data.fiat_base_multiplier

if data.username and data.username != link.username:
await check_username_exists(data.username)
# if wallet is not provided, use the wallet of the key
if not data.wallet:
data.wallet = key_info.wallet.id

for k, v in data.dict().items():
setattr(link, k, v)
new_wallet = await get_wallet(data.wallet)
if not new_wallet:
raise HTTPException(
detail="Wallet does not exist.", status_code=HTTPStatus.FORBIDDEN
)
if not user.admin and new_wallet.id not in user.wallet_ids:
raise HTTPException(detail="Not your wallet.", status_code=HTTPStatus.FORBIDDEN)

link = await update_pay_link(link)
else:
if data.username:
await check_username_exists(data.username)
if data.username and data.username != link.username:
await check_username_exists(data.username)

link = await create_pay_link(data)
for k, v in data.dict().items():
setattr(link, k, v)

link = await update_pay_link(link)
link.lnurl = check_lnurl_encode(req, link)
return link

Expand Down
Loading