|
1 | 1 | import { DurableObject } from 'cloudflare:workers';
|
2 |
| -import { sha256 } from '@noble/hashes/sha256'; |
3 |
| -import { bytesToHex } from '@stacks/common'; |
4 | 2 | import { verifyMessageSignatureRsv } from '@stacks/encryption';
|
5 |
| -import { |
6 |
| - encodeStructuredData, |
7 |
| - getAddressFromPublicKey, |
8 |
| - publicKeyFromSignatureRsv, |
9 |
| - stringAsciiCV, |
10 |
| - tupleCV, |
11 |
| - uintCV, |
12 |
| - validateStacksAddress, |
13 |
| -} from '@stacks/transactions'; |
| 3 | +import { getAddressFromPublicKey, validateStacksAddress } from '@stacks/transactions'; |
14 | 4 | import { Env } from '../../worker-configuration';
|
15 | 5 | import { AppConfig } from '../config';
|
16 | 6 | import { createJsonResponse } from '../utils/requests-responses';
|
@@ -96,71 +86,66 @@ export class AuthDO extends DurableObject<Env> {
|
96 | 86 | if (endpoint === '/request-auth-token') {
|
97 | 87 | // get signature from body
|
98 | 88 | const body = await request.json();
|
99 |
| - if (!body || typeof body !== 'object' || !('data' in body)) { |
| 89 | + if (!body || typeof body !== 'object' || !('signature' in body) || !('publicKey' in body)) { |
100 | 90 | return createJsonResponse(
|
101 | 91 | {
|
102 |
| - error: 'Missing or invalid "data" in request body', |
| 92 | + error: 'Missing or invalid "signature" or "publicKey" in request body', |
103 | 93 | },
|
104 | 94 | 400
|
105 | 95 | );
|
106 | 96 | }
|
107 |
| - const signedMessage = String(body.data); |
| 97 | + const signedMessage = String(body.signature); |
| 98 | + const publicKey = String(body.publicKey); |
108 | 99 | // try to verify signature
|
109 | 100 | try {
|
110 |
| - // TODO: this needs a home, corresponds with front-end settings |
111 |
| - // might fail signature check if it does not match |
112 |
| - const expectedDomain = tupleCV({ |
113 |
| - name: stringAsciiCV('sprint.aibtc.dev'), |
114 |
| - version: stringAsciiCV('0.0.1'), |
115 |
| - 'chain-id': uintCV(1), // hardcoded mainnet, testnet is u2147483648 |
116 |
| - }); |
117 |
| - // same here this has to match front-end |
118 |
| - const expectedMessage = stringAsciiCV('Welcome to aibtcdev!'); |
119 |
| - const encodedMessage = encodeStructuredData({ message: expectedMessage, domain: expectedDomain }); |
120 |
| - const encodedMessageHashed = sha256(encodedMessage); |
121 |
| - // get public key from signature |
122 |
| - const publicKey = publicKeyFromSignatureRsv(bytesToHex(encodedMessageHashed), signedMessage); |
123 |
| - // verify signature |
| 101 | + const expectedMessageText = 'welcome to aibtcdev!'; |
124 | 102 | const isSignatureVerified = verifyMessageSignatureRsv({
|
125 | 103 | signature: signedMessage, // what they sent us
|
126 |
| - message: encodedMessageHashed, // what we expect |
127 |
| - publicKey: publicKey, // public key from signature |
| 104 | + message: expectedMessageText, // what we expect |
| 105 | + publicKey, // public key from signature |
128 | 106 | });
|
| 107 | + const addressFromPubkey = getAddressFromPublicKey(publicKey, 'mainnet'); |
| 108 | + const isAddressValid = validateStacksAddress(addressFromPubkey); |
| 109 | + // check if signature is valid with the public key |
129 | 110 | if (!isSignatureVerified) {
|
130 | 111 | return createJsonResponse(
|
131 | 112 | {
|
132 |
| - error: `Failed to verify signature ${signedMessage}`, |
| 113 | + error: `Signature verification failed for public key ${publicKey}`, |
133 | 114 | },
|
134 | 115 | 401
|
135 | 116 | );
|
136 | 117 | }
|
137 |
| - // get address from public key |
138 |
| - const addressFromPublicKey = getAddressFromPublicKey(publicKey, 'mainnet'); |
139 |
| - // verify valid stacks address returned |
140 |
| - if (!validateStacksAddress(addressFromPublicKey)) { |
| 118 | + // check if address is valid |
| 119 | + if (!isAddressValid) { |
141 | 120 | return createJsonResponse(
|
142 | 121 | {
|
143 |
| - error: `Failed to get address from public key ${publicKey}`, |
| 122 | + error: `Invalid address ${addressFromPubkey} from public key ${publicKey}`, |
144 | 123 | },
|
145 |
| - 401 |
| 124 | + 400 |
146 | 125 | );
|
147 | 126 | }
|
148 | 127 | // add address to kv key list with unique session key
|
149 | 128 | // expires after 30 days and requires new signature from user
|
150 | 129 | // signing before expiration extends the expiration
|
151 | 130 | const sessionToken = crypto.randomUUID();
|
152 |
| - // first key allows us to filter by address |
153 |
| - await this.env.AIBTCDEV_SERVICES_KV.put(`${this.KEY_PREFIX}:address:${addressFromPublicKey}`, sessionToken, { |
| 131 | + // allow lookup of pubkey from address |
| 132 | + const savePubkey = this.env.AIBTCDEV_SERVICES_KV.put(`${this.KEY_PREFIX}:pubkey:${addressFromPubkey}`, publicKey, { |
| 133 | + expirationTtl: this.CACHE_TTL, |
| 134 | + }); |
| 135 | + // allow lookup of address from session token |
| 136 | + const saveSessionToken = this.env.AIBTCDEV_SERVICES_KV.put(`${this.KEY_PREFIX}:session:${sessionToken}`, addressFromPubkey, { |
154 | 137 | expirationTtl: this.CACHE_TTL,
|
155 | 138 | });
|
156 |
| - // second key allows us to filter by session key |
157 |
| - await this.env.AIBTCDEV_SERVICES_KV.put(`${this.KEY_PREFIX}:session:${sessionToken}`, addressFromPublicKey, { |
| 139 | + // allow lookup of session token from address |
| 140 | + const saveAddress = this.env.AIBTCDEV_SERVICES_KV.put(`${this.KEY_PREFIX}:address:${addressFromPubkey}`, sessionToken, { |
158 | 141 | expirationTtl: this.CACHE_TTL,
|
159 | 142 | });
|
| 143 | + // wait for all kv operations to complete |
| 144 | + await Promise.all([savePubkey, saveSessionToken, saveAddress]); |
160 | 145 | // return 200 with session token
|
161 | 146 | return createJsonResponse({
|
162 | 147 | message: 'auth token successfully created',
|
163 |
| - address: addressFromPublicKey, |
| 148 | + address: addressFromPubkey, |
164 | 149 | sessionToken,
|
165 | 150 | });
|
166 | 151 | } catch (error) {
|
|
0 commit comments