Skip to content

Commit 3660d18

Browse files
authored
Merge pull request #192 from PortalTechnologiesInc/wheatley/remove-age-verification-code
refactor(rest): remove verification-specific routes and settings
2 parents 59ce9c5 + 98f85ae commit 3660d18

21 files changed

Lines changed: 16 additions & 841 deletions

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
1414
- `MessageRouter` now exposes `SendOutcome` / `EventSendResult` from the underlying `portal` crate, giving relay delivery feedback per event. `SendOutcome::Delivered { relays }` includes the URLs of relays that accepted the event. Not yet surfaced on the REST API; wiring will follow in a future release (#85)
1515
- `portal-rates`: added fallback-only market source failover in `MarketAPI` (tries fallback providers when the primary source fails). No `fiatUnits` mapping changes in this update (#129).
1616

17+
#### Changed
18+
- Payment request amount fields now use `Amount` wrapper (`serde(transparent)` over `u64`) in core request models; wire JSON format remains unchanged.
19+
20+
#### Removed
21+
- Verification-specific REST endpoints:
22+
- `POST /verification/sessions`
23+
- `POST /verification/token`
24+
- Verification-specific runtime settings from `portal-rest` config (`[verification]` / `PORTAL__VERIFICATION__API_KEY`).
25+
- Verification-specific entries from `portal-rest` OpenAPI spec and generated TS client/types.
26+
1727
---
1828

1929
### [0.4.1] - 2026-04-01
@@ -105,6 +115,7 @@ First release — Docker image available at [`getportal/sdk-daemon`](https://hub
105115

106116
#### Changed
107117
- `register_nip05()` now delegates to `portal::register_nip05()` (moved to `portal` crate). UniFFI bindings unchanged.
118+
- Payment request amount fields now use `Amount` wrapper (`serde(transparent)` over `u64`) in core models; wire format and app compatibility unchanged.
108119

109120
---
110121

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,12 @@ No accounts. No KYC. No payment processor.
1515

1616
---
1717

18-
Portal lets you add **age verification**, **authentication**, and **payments** to your app — without collecting personal data.
18+
Portal lets you add **authentication** and **payments** to your app — without collecting personal data.
1919

2020
> **New here?** Start with the [documentation](https://portaltechnologiesinc.github.io/lib/) or create a free instance on [PortalHub](https://hub.getportal.cc).
2121
2222
## What can you build?
2323

24-
- **Age verification** — verify users' age for compliance, no personal data stored → [Guide](https://portaltechnologiesinc.github.io/lib/age-verification/getting-started.html)
2524
- **Authentication** — passwordless login via the Portal app → [Guide](https://portaltechnologiesinc.github.io/lib/platform/authentication.html)
2625
- **Payments** — single, recurring, invoice-based; BTC or fiat → [Guide](https://portaltechnologiesinc.github.io/lib/platform/single-payments.html)
2726
- **Digital tickets** — issue and verify Cashu tokens → [Guide](https://portaltechnologiesinc.github.io/lib/platform/cashu-tokens.html)

crates/portal-rest/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Portal REST API
22

3-
REST API server for Portal — authentication, payments, age verification, and more. Use it via the [TypeScript SDK](https://www.npmjs.com/package/portal-sdk) or call the HTTP endpoints directly.
3+
REST API server for Portal — authentication, payments, and more. Use it via the [TypeScript SDK](https://www.npmjs.com/package/portal-sdk) or call the HTTP endpoints directly.
44

55
> 📖 **[Full documentation](https://portaltechnologiesinc.github.io/lib/)** · 🚀 **[Get started with PortalHub](https://hub.getportal.cc)** (no self-hosting needed)
66

crates/portal-rest/clients/ts/src/client.ts

Lines changed: 0 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -752,48 +752,4 @@ export class PortalClient {
752752
return this.get<EventsResponse>(`/events/${encodeURIComponent(streamId)}${q}`);
753753
}
754754

755-
// ---- Verification ----
756-
757-
/**
758-
* Initiate a browser-based age verification session.
759-
*
760-
* Creates the verification session AND automatically starts listening for the
761-
* verification token. Returns session info plus an `AsyncOperation` — use
762-
* `poll()` or `done` to wait for the Cashu token result.
763-
*
764-
* Requires `[verification] api_key` in portal-rest config.
765-
*/
766-
public async createVerificationSession(relayUrls?: string[]): Promise<
767-
VerificationSessionResponse & AsyncOperation<CashuResponseStatus>
768-
> {
769-
const resp = await this.post<VerificationSessionResponse>('/verification/sessions', {
770-
relays: relayUrls,
771-
});
772-
const done = this.registerStream(resp.stream_id).then(
773-
(event) => event.status as CashuResponseStatus
774-
);
775-
return { ...resp, streamId: resp.stream_id, done };
776-
}
777-
778-
// ---- Verification Token ----
779-
780-
/**
781-
* Request a Cashu token from a recipient Portal wallet.
782-
*
783-
* Uses the Portal mint (`https://mint.getportal.cc`) with unit `multi`.
784-
* Returns the `stream_id`; poll via `getEvents(streamId)` or use `onEvent`.
785-
*/
786-
public async requestVerificationToken(
787-
recipientKey: string,
788-
subkeys: string[]
789-
): Promise<AsyncOperation<CashuResponseStatus>> {
790-
const resp = await this.post<{ stream_id: string }>('/verification/token', {
791-
recipient_key: recipientKey,
792-
subkeys,
793-
});
794-
const done = this.registerStream(resp.stream_id).then(
795-
(event) => event.status as CashuResponseStatus
796-
);
797-
return { streamId: resp.stream_id, done };
798-
}
799755
}

crates/portal-rest/clients/ts/src/types.ts

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -321,27 +321,6 @@ export type CashuResponseStatus =
321321
| { status: 'insufficient_funds' }
322322
| { status: 'rejected'; reason?: string };
323323

324-
// ---- Verification ----
325-
326-
export interface CreateVerificationSessionRequest {
327-
/** Relay URLs to use for the verification session. Defaults to the relays configured in the [nostr] section of the config if omitted. */
328-
relays?: string[];
329-
}
330-
331-
export interface VerificationSessionResponse {
332-
session_id: string;
333-
session_url: string;
334-
ephemeral_npub: string;
335-
expires_at: number;
336-
stream_id: string;
337-
}
338-
339-
// ---- Portal Token (Cashu from Portal wallet) ----
340-
341-
export interface RequestVerificationTokenRequest {
342-
recipient_key: string;
343-
subkeys: string[];
344-
}
345324

346325
// ---- Relays ----
347326

crates/portal-rest/example.config.toml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,6 @@ ln_backend = "none"
5252
path = "portal-rest.db"
5353

5454

55-
## Age verification settings. Required for the POST /verification/sessions endpoint.
56-
## Get your API key from https://verify.getportal.cc
57-
# [verification]
58-
# api_key = "your-api-key-here"
59-
6055

6156
## Optional Nostr profile metadata. Set any combination of fields to publish
6257
## your profile on the Nostr network at startup. Omit the section or leave

crates/portal-rest/openapi.yaml

Lines changed: 0 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -976,69 +976,3 @@ paths:
976976
"404":
977977
description: Stream not found
978978

979-
980-
/verification/sessions:
981-
post:
982-
tags:
983-
- Verification
984-
summary: Initiate a browser-based age verification session
985-
description: |
986-
Creates a verification session AND automatically starts listening for the
987-
verification token. Returns session info (including `session_url` to redirect
988-
the user to) plus a `stream_id` to poll for the Cashu token result.
989-
Poll `GET /events/{stream_id}` for a `cashu_response` event containing the token.
990-
Requires `[verification] api_key` in the portal-rest configuration.
991-
requestBody:
992-
required: false
993-
content:
994-
application/json:
995-
schema:
996-
$ref: '#/components/schemas/CreateVerificationSessionRequest'
997-
responses:
998-
"200":
999-
description: Verification session created
1000-
content:
1001-
application/json:
1002-
schema:
1003-
allOf:
1004-
- $ref: '#/components/schemas/ApiResponse'
1005-
- properties:
1006-
data:
1007-
$ref: '#/components/schemas/VerificationSessionResponse'
1008-
"400":
1009-
description: Verification not configured or invalid request
1010-
"500":
1011-
description: Verification service error
1012-
1013-
/verification/token:
1014-
post:
1015-
tags:
1016-
- Verification
1017-
summary: Request a verification token from a user who already holds one
1018-
description: |
1019-
Requests a verification token (Cashu) from a recipient who already has a token
1020-
in their Portal wallet (e.g. a mobile app user who completed verification previously).
1021-
Uses the Portal mint (`https://mint.getportal.cc`) with unit `multi`. Returns a
1022-
stream_id for polling. Poll GET /events/{stream_id} for the `cashu_response` event.
1023-
1024-
For new browser-based verification, use `POST /verification/sessions` instead — it
1025-
handles the full flow (session creation + token listening) in a single call.
1026-
requestBody:
1027-
required: true
1028-
content:
1029-
application/json:
1030-
schema:
1031-
$ref: '#/components/schemas/RequestVerificationTokenRequest'
1032-
responses:
1033-
"201":
1034-
description: Verification token request initiated
1035-
content:
1036-
application/json:
1037-
schema:
1038-
allOf:
1039-
- $ref: '#/components/schemas/ApiResponse'
1040-
- properties:
1041-
data:
1042-
$ref: '#/components/schemas/StreamIdResponse'
1043-
"400":
1044-
description: Invalid recipient key or subkeys

crates/portal-rest/src/command.rs

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -74,18 +74,6 @@ pub struct RequestCashuRequest {
7474
pub amount: u64,
7575
}
7676

77-
#[derive(Debug, Deserialize)]
78-
pub struct CreateVerificationSessionRequest {
79-
/// Relay URLs to use for the verification session. Defaults to
80-
/// the relays configured in the `[nostr]` section of the config if not set.
81-
pub relays: Option<Vec<String>>,
82-
}
83-
84-
#[derive(Debug, Deserialize)]
85-
pub struct RequestVerificationTokenRequest {
86-
pub recipient_key: String,
87-
pub subkeys: Vec<String>,
88-
}
8977

9078
#[derive(Debug, Deserialize)]
9179
pub struct SendCashuDirectRequest {

crates/portal-rest/src/config.rs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,6 @@ pub struct Settings {
1818
pub profile: ProfileSettings,
1919
#[serde(default)]
2020
pub logging: LoggingSettings,
21-
#[serde(default)]
22-
pub verification: Option<VerificationSettings>,
23-
}
24-
25-
#[derive(Deserialize, Debug, Clone)]
26-
pub struct VerificationSettings {
27-
pub api_key: String,
2821
}
2922

3023
#[derive(Deserialize, Debug, Clone)]

crates/portal-rest/src/handlers.rs

Lines changed: 0 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -969,154 +969,3 @@ pub async fn get_events(
969969
Ok(ok(EventsResponse { stream_id, events }))
970970
}
971971

972-
// ── Age Verification constants ────────────────────────────────────────────────
973-
const VERIFICATION_SERVICE_URL: &str = "https://verify.getportal.cc/verify/sessions";
974-
const VERIFICATION_MINT_URL: &str = "https://mint.getportal.cc";
975-
const VERIFICATION_TICKET_UNIT: &str = "multi";
976-
const VERIFICATION_TOKEN_AMOUNT: u64 = 1;
977-
978-
// POST /verification/sessions
979-
pub async fn create_verification_session(
980-
State(state): State<AppState>,
981-
Json(req): Json<crate::command::CreateVerificationSessionRequest>,
982-
) -> ApiResult<VerificationSessionResponse> {
983-
let verification = state
984-
.settings
985-
.verification
986-
.as_ref()
987-
.ok_or_else(|| bad_request("Verification not configured — add [verification] api_key to config"))?;
988-
989-
let relays = req.relays.unwrap_or_else(|| state.settings.nostr.relays.clone());
990-
991-
#[derive(serde::Serialize)]
992-
struct VerifySessionRequest {
993-
relays: Vec<String>,
994-
}
995-
996-
#[derive(serde::Deserialize)]
997-
struct VerifySessionApiResponse {
998-
session_id: String,
999-
session_url: String,
1000-
ephemeral_npub: String,
1001-
expires_at: u64,
1002-
}
1003-
1004-
let client = reqwest::Client::new();
1005-
let resp = client
1006-
.post(VERIFICATION_SERVICE_URL)
1007-
.header("X-Api-Key", &verification.api_key)
1008-
.json(&VerifySessionRequest { relays })
1009-
.send()
1010-
.await
1011-
.map_err(|e| internal_error(format!("Failed to call verification service: {e}")))?;
1012-
1013-
if !resp.status().is_success() {
1014-
let status = resp.status();
1015-
let body = resp.text().await.unwrap_or_default();
1016-
return Err(internal_error(format!(
1017-
"Verification service returned {}: {}",
1018-
status, body
1019-
)));
1020-
}
1021-
1022-
let api_resp: VerifySessionApiResponse = resp
1023-
.json()
1024-
.await
1025-
.map_err(|e| internal_error(format!("Failed to parse verification response: {e}")))?;
1026-
1027-
// Parse the ephemeral npub (bech32 format) into a PublicKey
1028-
let ephemeral_key = PublicKey::parse(&api_resp.ephemeral_npub)
1029-
.map_err(|e| internal_error(format!("Invalid ephemeral_npub from verification service: {e}")))?;
1030-
1031-
let stream_id = spawn_verification_token_request(
1032-
state.sdk.clone(),
1033-
&state.events,
1034-
ephemeral_key,
1035-
vec![],
1036-
)
1037-
.await;
1038-
1039-
Ok(ok(VerificationSessionResponse {
1040-
session_id: api_resp.session_id,
1041-
session_url: api_resp.session_url,
1042-
ephemeral_npub: api_resp.ephemeral_npub,
1043-
expires_at: api_resp.expires_at,
1044-
stream_id,
1045-
}))
1046-
}
1047-
1048-
/// Shared helper: creates a verification token request stream, spawns a
1049-
/// background task that calls `sdk.request_cashu`, and returns the `stream_id`.
1050-
async fn spawn_verification_token_request(
1051-
sdk: Arc<portal_sdk::PortalSDK>,
1052-
events: &crate::events::EventStore,
1053-
recipient: PublicKey,
1054-
subkeys: Vec<PublicKey>,
1055-
) -> String {
1056-
let expires_at = Timestamp::now_plus_seconds(300);
1057-
let content = CashuRequestContent {
1058-
mint_url: VERIFICATION_MINT_URL.to_string(),
1059-
unit: VERIFICATION_TICKET_UNIT.to_string(),
1060-
amount: VERIFICATION_TOKEN_AMOUNT,
1061-
request_id: Uuid::new_v4().to_string(),
1062-
expires_at,
1063-
};
1064-
1065-
let stream_id = events
1066-
.new_stream("cashu_portal_token_request", None)
1067-
.await;
1068-
1069-
let events = events.clone();
1070-
let sid = stream_id.clone();
1071-
tokio::spawn(async move {
1072-
match sdk.request_cashu(recipient, subkeys, content).await {
1073-
Ok(Some(r)) => {
1074-
events
1075-
.push(&sid, NotificationData::CashuResponse { status: r.status })
1076-
.await;
1077-
}
1078-
Ok(None) => {
1079-
events
1080-
.push(
1081-
&sid,
1082-
NotificationData::Error {
1083-
reason: "No response from recipient".to_string(),
1084-
},
1085-
)
1086-
.await;
1087-
}
1088-
Err(e) => {
1089-
events
1090-
.push(
1091-
&sid,
1092-
NotificationData::Error {
1093-
reason: format!("Failed to request verification token: {e}"),
1094-
},
1095-
)
1096-
.await;
1097-
}
1098-
}
1099-
});
1100-
1101-
stream_id
1102-
}
1103-
1104-
// POST /verification/token
1105-
pub async fn request_verification_token(
1106-
State(state): State<AppState>,
1107-
Json(req): Json<crate::command::RequestVerificationTokenRequest>,
1108-
) -> ApiResult<StreamResponse> {
1109-
let recipient_key =
1110-
hex_to_pubkey(&req.recipient_key).map_err(|e| bad_request(format!("Invalid recipient key: {e}")))?;
1111-
let subkeys = parse_subkeys(&req.subkeys).map_err(|e| bad_request(format!("Invalid subkeys: {e}")))?;
1112-
1113-
let stream_id = spawn_verification_token_request(
1114-
state.sdk.clone(),
1115-
&state.events,
1116-
recipient_key,
1117-
subkeys,
1118-
)
1119-
.await;
1120-
1121-
Ok(created(StreamResponse { stream_id }))
1122-
}

0 commit comments

Comments
 (0)