viem 2.11.1 → 2.12.0
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +16 -0
- package/README.md +3 -3
- package/_cjs/actions/public/verifyMessage.js.map +1 -1
- package/_cjs/actions/siwe/verifySiweMessage.js +32 -0
- package/_cjs/actions/siwe/verifySiweMessage.js.map +1 -0
- package/_cjs/chains/definitions/flowPreviewnet.js +6 -0
- package/_cjs/chains/definitions/flowPreviewnet.js.map +1 -1
- package/_cjs/chains/definitions/jbcTestnet.js +0 -4
- package/_cjs/chains/definitions/jbcTestnet.js.map +1 -1
- package/_cjs/chains/definitions/l3x.js +24 -0
- package/_cjs/chains/definitions/l3x.js.map +1 -0
- package/_cjs/chains/definitions/l3xTestnet.js +24 -0
- package/_cjs/chains/definitions/l3xTestnet.js.map +1 -0
- package/_cjs/chains/definitions/metis.js +5 -0
- package/_cjs/chains/definitions/metis.js.map +1 -1
- package/_cjs/chains/definitions/thaiChain.js +29 -0
- package/_cjs/chains/definitions/thaiChain.js.map +1 -0
- package/_cjs/chains/index.js +10 -4
- package/_cjs/chains/index.js.map +1 -1
- package/_cjs/clients/decorators/public.js +2 -0
- package/_cjs/clients/decorators/public.js.map +1 -1
- package/_cjs/errors/siwe.js +22 -0
- package/_cjs/errors/siwe.js.map +1 -0
- package/_cjs/errors/version.js +1 -1
- package/_cjs/siwe/index.js +16 -0
- package/_cjs/siwe/index.js.map +1 -0
- package/_cjs/utils/siwe/createSiweMessage.js +122 -0
- package/_cjs/utils/siwe/createSiweMessage.js.map +1 -0
- package/_cjs/utils/siwe/generateSiweNonce.js +9 -0
- package/_cjs/utils/siwe/generateSiweNonce.js.map +1 -0
- package/_cjs/utils/siwe/parseSiweMessage.js +25 -0
- package/_cjs/utils/siwe/parseSiweMessage.js.map +1 -0
- package/_cjs/utils/siwe/types.js +3 -0
- package/_cjs/utils/siwe/types.js.map +1 -0
- package/_cjs/utils/siwe/utils.js +44 -0
- package/_cjs/utils/siwe/utils.js.map +1 -0
- package/_cjs/utils/siwe/validateSiweMessage.js +29 -0
- package/_cjs/utils/siwe/validateSiweMessage.js.map +1 -0
- package/_esm/actions/public/verifyMessage.js.map +1 -1
- package/_esm/actions/siwe/verifySiweMessage.js +39 -0
- package/_esm/actions/siwe/verifySiweMessage.js.map +1 -0
- package/_esm/chains/definitions/flowPreviewnet.js +6 -0
- package/_esm/chains/definitions/flowPreviewnet.js.map +1 -1
- package/_esm/chains/definitions/jbcTestnet.js +0 -4
- package/_esm/chains/definitions/jbcTestnet.js.map +1 -1
- package/_esm/chains/definitions/l3x.js +21 -0
- package/_esm/chains/definitions/l3x.js.map +1 -0
- package/_esm/chains/definitions/l3xTestnet.js +21 -0
- package/_esm/chains/definitions/l3xTestnet.js.map +1 -0
- package/_esm/chains/definitions/metis.js +5 -0
- package/_esm/chains/definitions/metis.js.map +1 -1
- package/_esm/chains/definitions/thaiChain.js +26 -0
- package/_esm/chains/definitions/thaiChain.js.map +1 -0
- package/_esm/chains/index.js +3 -0
- package/_esm/chains/index.js.map +1 -1
- package/_esm/clients/decorators/public.js +2 -0
- package/_esm/clients/decorators/public.js.map +1 -1
- package/_esm/errors/siwe.js +18 -0
- package/_esm/errors/siwe.js.map +1 -0
- package/_esm/errors/version.js +1 -1
- package/_esm/siwe/index.js +7 -0
- package/_esm/siwe/index.js.map +1 -0
- package/_esm/utils/siwe/createSiweMessage.js +137 -0
- package/_esm/utils/siwe/createSiweMessage.js.map +1 -0
- package/_esm/utils/siwe/generateSiweNonce.js +15 -0
- package/_esm/utils/siwe/generateSiweNonce.js.map +1 -0
- package/_esm/utils/siwe/parseSiweMessage.js +30 -0
- package/_esm/utils/siwe/parseSiweMessage.js.map +1 -0
- package/_esm/utils/siwe/types.js +2 -0
- package/_esm/utils/siwe/types.js.map +1 -0
- package/_esm/utils/siwe/utils.js +49 -0
- package/_esm/utils/siwe/utils.js.map +1 -0
- package/_esm/utils/siwe/validateSiweMessage.js +30 -0
- package/_esm/utils/siwe/validateSiweMessage.js.map +1 -0
- package/_types/actions/public/verifyMessage.d.ts +3 -2
- package/_types/actions/public/verifyMessage.d.ts.map +1 -1
- package/_types/actions/siwe/verifySiweMessage.d.ts +34 -0
- package/_types/actions/siwe/verifySiweMessage.d.ts.map +1 -0
- package/_types/chains/definitions/flowPreviewnet.d.ts +6 -8
- package/_types/chains/definitions/flowPreviewnet.d.ts.map +1 -1
- package/_types/chains/definitions/jbcTestnet.d.ts +0 -4
- package/_types/chains/definitions/jbcTestnet.d.ts.map +1 -1
- package/_types/chains/definitions/l3x.d.ts +37 -0
- package/_types/chains/definitions/l3x.d.ts.map +1 -0
- package/_types/chains/definitions/l3xTestnet.d.ts +37 -0
- package/_types/chains/definitions/l3xTestnet.d.ts.map +1 -0
- package/_types/chains/definitions/metis.d.ts +5 -0
- package/_types/chains/definitions/metis.d.ts.map +1 -1
- package/_types/chains/definitions/thaiChain.d.ts +34 -0
- package/_types/chains/definitions/thaiChain.d.ts.map +1 -0
- package/_types/chains/index.d.ts +3 -0
- package/_types/chains/index.d.ts.map +1 -1
- package/_types/clients/decorators/public.d.ts +30 -0
- package/_types/clients/decorators/public.d.ts.map +1 -1
- package/_types/errors/siwe.d.ts +13 -0
- package/_types/errors/siwe.d.ts.map +1 -0
- package/_types/errors/version.d.ts +1 -1
- package/_types/siwe/index.d.ts +8 -0
- package/_types/siwe/index.d.ts.map +1 -0
- package/_types/utils/siwe/createSiweMessage.d.ts +24 -0
- package/_types/utils/siwe/createSiweMessage.d.ts.map +1 -0
- package/_types/utils/siwe/generateSiweNonce.d.ts +12 -0
- package/_types/utils/siwe/generateSiweNonce.d.ts.map +1 -0
- package/_types/utils/siwe/parseSiweMessage.d.ts +11 -0
- package/_types/utils/siwe/parseSiweMessage.d.ts.map +1 -0
- package/_types/utils/siwe/types.d.ts +61 -0
- package/_types/utils/siwe/types.d.ts.map +1 -0
- package/_types/utils/siwe/utils.d.ts +2 -0
- package/_types/utils/siwe/utils.d.ts.map +1 -0
- package/_types/utils/siwe/validateSiweMessage.d.ts +39 -0
- package/_types/utils/siwe/validateSiweMessage.d.ts.map +1 -0
- package/actions/public/verifyMessage.ts +11 -8
- package/actions/siwe/verifySiweMessage.ts +90 -0
- package/chains/definitions/flowPreviewnet.ts +6 -0
- package/chains/definitions/jbcTestnet.ts +0 -4
- package/chains/definitions/l3x.ts +21 -0
- package/chains/definitions/l3xTestnet.ts +21 -0
- package/chains/definitions/metis.ts +6 -0
- package/chains/definitions/thaiChain.ts +26 -0
- package/chains/index.ts +3 -0
- package/clients/decorators/public.ts +37 -0
- package/errors/siwe.ts +20 -0
- package/errors/version.ts +1 -1
- package/package.json +9 -1
- package/siwe/index.ts +29 -0
- package/siwe/package.json +6 -0
- package/utils/siwe/createSiweMessage.ts +168 -0
- package/utils/siwe/generateSiweNonce.ts +15 -0
- package/utils/siwe/parseSiweMessage.ts +55 -0
- package/utils/siwe/types.ts +61 -0
- package/utils/siwe/utils.ts +51 -0
- package/utils/siwe/validateSiweMessage.ts +70 -0
@@ -0,0 +1,168 @@
|
|
1
|
+
import {
|
2
|
+
SiweInvalidMessageFieldError,
|
3
|
+
type SiweInvalidMessageFieldErrorType,
|
4
|
+
} from '../../errors/siwe.js'
|
5
|
+
import type { ErrorType } from '../../errors/utils.js'
|
6
|
+
import { type GetAddressErrorType, getAddress } from '../address/getAddress.js'
|
7
|
+
import type { SiweMessage } from './types.js'
|
8
|
+
import { isUri } from './utils.js'
|
9
|
+
|
10
|
+
export type CreateSiweMessageParameters = SiweMessage
|
11
|
+
|
12
|
+
export type CreateSiweMessageReturnType = string
|
13
|
+
|
14
|
+
export type CreateSiweMessageErrorType =
|
15
|
+
| GetAddressErrorType
|
16
|
+
| SiweInvalidMessageFieldErrorType
|
17
|
+
| ErrorType
|
18
|
+
|
19
|
+
/**
|
20
|
+
* @description Creates EIP-4361 formatted message.
|
21
|
+
*
|
22
|
+
* @example
|
23
|
+
* const message = createMessage({
|
24
|
+
* address: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e',
|
25
|
+
* chainId: 1,
|
26
|
+
* domain: 'example.com',
|
27
|
+
* nonce: 'foobarbaz',
|
28
|
+
* uri: 'https://example.com/path',
|
29
|
+
* version: '1',
|
30
|
+
* })
|
31
|
+
*
|
32
|
+
* @see https://eips.ethereum.org/EIPS/eip-4361
|
33
|
+
*/
|
34
|
+
export function createSiweMessage(
|
35
|
+
parameters: CreateSiweMessageParameters,
|
36
|
+
): CreateSiweMessageReturnType {
|
37
|
+
const {
|
38
|
+
chainId,
|
39
|
+
domain,
|
40
|
+
expirationTime,
|
41
|
+
issuedAt = new Date(),
|
42
|
+
nonce,
|
43
|
+
notBefore,
|
44
|
+
requestId,
|
45
|
+
resources,
|
46
|
+
scheme,
|
47
|
+
uri,
|
48
|
+
version,
|
49
|
+
} = parameters
|
50
|
+
|
51
|
+
// Validate fields
|
52
|
+
{
|
53
|
+
// Required fields
|
54
|
+
if (chainId !== Math.floor(chainId))
|
55
|
+
throw new SiweInvalidMessageFieldError({
|
56
|
+
field: 'chainId',
|
57
|
+
metaMessages: [
|
58
|
+
'- Chain ID must be a EIP-155 chain ID.',
|
59
|
+
'- See https://eips.ethereum.org/EIPS/eip-155',
|
60
|
+
'',
|
61
|
+
`Provided value: ${chainId}`,
|
62
|
+
],
|
63
|
+
})
|
64
|
+
if (!domainRegex.test(domain))
|
65
|
+
throw new SiweInvalidMessageFieldError({
|
66
|
+
field: 'domain',
|
67
|
+
metaMessages: [
|
68
|
+
'- Domain must be an RFC 3986 authority.',
|
69
|
+
'- See https://www.rfc-editor.org/rfc/rfc3986',
|
70
|
+
'',
|
71
|
+
`Provided value: ${domain}`,
|
72
|
+
],
|
73
|
+
})
|
74
|
+
if (!nonceRegex.test(nonce))
|
75
|
+
throw new SiweInvalidMessageFieldError({
|
76
|
+
field: 'nonce',
|
77
|
+
metaMessages: [
|
78
|
+
'- Nonce must be at least 8 characters.',
|
79
|
+
'- Nonce must be alphanumeric.',
|
80
|
+
'',
|
81
|
+
`Provided value: ${nonce}`,
|
82
|
+
],
|
83
|
+
})
|
84
|
+
if (!isUri(uri))
|
85
|
+
throw new SiweInvalidMessageFieldError({
|
86
|
+
field: 'uri',
|
87
|
+
metaMessages: [
|
88
|
+
'- URI must be a RFC 3986 URI referring to the resource that is the subject of the signing.',
|
89
|
+
'- See https://www.rfc-editor.org/rfc/rfc3986',
|
90
|
+
'',
|
91
|
+
`Provided value: ${uri}`,
|
92
|
+
],
|
93
|
+
})
|
94
|
+
if (version !== '1')
|
95
|
+
throw new SiweInvalidMessageFieldError({
|
96
|
+
field: 'version',
|
97
|
+
metaMessages: [
|
98
|
+
"- Version must be '1'.",
|
99
|
+
'',
|
100
|
+
`Provided value: ${version}`,
|
101
|
+
],
|
102
|
+
})
|
103
|
+
|
104
|
+
// Optional fields
|
105
|
+
if (scheme && !schemeRegex.test(scheme))
|
106
|
+
throw new SiweInvalidMessageFieldError({
|
107
|
+
field: 'scheme',
|
108
|
+
metaMessages: [
|
109
|
+
'- Scheme must be an RFC 3986 URI scheme.',
|
110
|
+
'- See https://www.rfc-editor.org/rfc/rfc3986#section-3.1',
|
111
|
+
'',
|
112
|
+
`Provided value: ${scheme}`,
|
113
|
+
],
|
114
|
+
})
|
115
|
+
const statement = parameters.statement
|
116
|
+
if (statement?.includes('\n'))
|
117
|
+
throw new SiweInvalidMessageFieldError({
|
118
|
+
field: 'statement',
|
119
|
+
metaMessages: [
|
120
|
+
"- Statement must not include '\\n'.",
|
121
|
+
'',
|
122
|
+
`Provided value: ${statement}`,
|
123
|
+
],
|
124
|
+
})
|
125
|
+
}
|
126
|
+
|
127
|
+
// Construct message
|
128
|
+
const address = getAddress(parameters.address)
|
129
|
+
const origin = (() => {
|
130
|
+
if (scheme) return `${scheme}://${domain}`
|
131
|
+
return domain
|
132
|
+
})()
|
133
|
+
const statement = (() => {
|
134
|
+
if (!parameters.statement) return ''
|
135
|
+
return `\n${parameters.statement}\n`
|
136
|
+
})()
|
137
|
+
const prefix = `${origin} wants you to sign in with your Ethereum account:\n${address}\n${statement}`
|
138
|
+
|
139
|
+
let suffix = `URI: ${uri}\nVersion: ${version}\nChain ID: ${chainId}\nNonce: ${nonce}\nIssued At: ${issuedAt.toISOString()}`
|
140
|
+
|
141
|
+
if (expirationTime)
|
142
|
+
suffix += `\nExpiration Time: ${expirationTime.toISOString()}`
|
143
|
+
if (notBefore) suffix += `\nNot Before: ${notBefore.toISOString()}`
|
144
|
+
if (requestId) suffix += `\nRequest ID: ${requestId}`
|
145
|
+
if (resources) {
|
146
|
+
let content = '\nResources:'
|
147
|
+
for (const resource of resources) {
|
148
|
+
if (!isUri(resource))
|
149
|
+
throw new SiweInvalidMessageFieldError({
|
150
|
+
field: 'resources',
|
151
|
+
metaMessages: [
|
152
|
+
'- Every resource must be a RFC 3986 URI.',
|
153
|
+
'- See https://www.rfc-editor.org/rfc/rfc3986',
|
154
|
+
'',
|
155
|
+
`Provided value: ${resource}`,
|
156
|
+
],
|
157
|
+
})
|
158
|
+
content += `\n- ${resource}`
|
159
|
+
}
|
160
|
+
suffix += content
|
161
|
+
}
|
162
|
+
|
163
|
+
return `${prefix}\n${suffix}`
|
164
|
+
}
|
165
|
+
|
166
|
+
const domainRegex = /^(?:(?:(?!-)[a-zA-Z0-9-]{1,63}(?<!-)\.)+[a-zA-Z]{2,63})$/
|
167
|
+
const nonceRegex = /^[a-zA-Z0-9]{8,}$/
|
168
|
+
const schemeRegex = /^([a-zA-Z][a-zA-Z0-9+-.]*)$/
|
@@ -0,0 +1,15 @@
|
|
1
|
+
import { uid } from '../../utils/uid.js'
|
2
|
+
|
3
|
+
/**
|
4
|
+
* @description Generates random EIP-4361 nonce.
|
5
|
+
*
|
6
|
+
* @example
|
7
|
+
* const nonce = generateNonce()
|
8
|
+
*
|
9
|
+
* @see https://eips.ethereum.org/EIPS/eip-4361
|
10
|
+
*
|
11
|
+
* @returns A randomly generated EIP-4361 nonce.
|
12
|
+
*/
|
13
|
+
export function generateSiweNonce(): string {
|
14
|
+
return uid(96)
|
15
|
+
}
|
@@ -0,0 +1,55 @@
|
|
1
|
+
import type { Address } from 'abitype'
|
2
|
+
|
3
|
+
import type { ExactPartial, Prettify } from '../../types/utils.js'
|
4
|
+
import type { SiweMessage } from './types.js'
|
5
|
+
|
6
|
+
/**
|
7
|
+
* @description Parses EIP-4361 formatted message into message fields object.
|
8
|
+
*
|
9
|
+
* @see https://eips.ethereum.org/EIPS/eip-4361
|
10
|
+
*
|
11
|
+
* @returns EIP-4361 fields object
|
12
|
+
*/
|
13
|
+
export function parseSiweMessage(
|
14
|
+
message: string,
|
15
|
+
): Prettify<ExactPartial<SiweMessage>> {
|
16
|
+
const { scheme, statement, ...prefix } = (message.match(prefixRegex)
|
17
|
+
?.groups ?? {}) as {
|
18
|
+
address: Address
|
19
|
+
domain: string
|
20
|
+
scheme?: string
|
21
|
+
statement?: string
|
22
|
+
}
|
23
|
+
const { chainId, expirationTime, issuedAt, notBefore, requestId, ...suffix } =
|
24
|
+
(message.match(suffixRegex)?.groups ?? {}) as {
|
25
|
+
chainId: string
|
26
|
+
expirationTime?: string
|
27
|
+
issuedAt?: string
|
28
|
+
nonce: string
|
29
|
+
notBefore?: string
|
30
|
+
requestId?: string
|
31
|
+
uri: string
|
32
|
+
version: '1'
|
33
|
+
}
|
34
|
+
const resources = message.split('Resources:')[1]?.split('\n- ').slice(1)
|
35
|
+
return {
|
36
|
+
...prefix,
|
37
|
+
...suffix,
|
38
|
+
...(chainId ? { chainId: Number(chainId) } : {}),
|
39
|
+
...(expirationTime ? { expirationTime: new Date(expirationTime) } : {}),
|
40
|
+
...(issuedAt ? { issuedAt: new Date(issuedAt) } : {}),
|
41
|
+
...(notBefore ? { notBefore: new Date(notBefore) } : {}),
|
42
|
+
...(requestId ? { requestId } : {}),
|
43
|
+
...(resources ? { resources } : {}),
|
44
|
+
...(scheme ? { scheme } : {}),
|
45
|
+
...(statement ? { statement } : {}),
|
46
|
+
}
|
47
|
+
}
|
48
|
+
|
49
|
+
// https://regexr.com/80gdj
|
50
|
+
const prefixRegex =
|
51
|
+
/^(?:(?<scheme>[a-zA-Z][a-zA-Z0-9+-.]*):\/\/)?(?<domain>[a-zA-Z0-9+-.]*) (?:wants you to sign in with your Ethereum account:\n)(?<address>0x[a-fA-F0-9]{40})\n\n(?:(?<statement>.*)\n\n)?/
|
52
|
+
|
53
|
+
// https://regexr.com/80gf9
|
54
|
+
const suffixRegex =
|
55
|
+
/(?:URI: (?<uri>.+))\n(?:Version: (?<version>.+))\n(?:Chain ID: (?<chainId>\d+))\n(?:Nonce: (?<nonce>[a-zA-Z0-9]+))\n(?:Issued At: (?<issuedAt>.+))(?:\nExpiration Time: (?<expirationTime>.+))?(?:\nNot Before: (?<notBefore>.+))?(?:\nRequest ID: (?<requestId>.+))?/
|
@@ -0,0 +1,61 @@
|
|
1
|
+
import type { Address } from 'abitype'
|
2
|
+
|
3
|
+
/**
|
4
|
+
* @description EIP-4361 message fields
|
5
|
+
*
|
6
|
+
* @see https://eips.ethereum.org/EIPS/eip-4361
|
7
|
+
*/
|
8
|
+
export type SiweMessage = {
|
9
|
+
/**
|
10
|
+
* The Ethereum address performing the signing.
|
11
|
+
*/
|
12
|
+
address: Address
|
13
|
+
/**
|
14
|
+
* The [EIP-155](https://eips.ethereum.org/EIPS/eip-155) Chain ID to which the session is bound,
|
15
|
+
*/
|
16
|
+
chainId: number
|
17
|
+
/**
|
18
|
+
* [RFC 3986](https://www.rfc-editor.org/rfc/rfc3986) authority that is requesting the signing.
|
19
|
+
*/
|
20
|
+
domain: string
|
21
|
+
/**
|
22
|
+
* Time when the signed authentication message is no longer valid.
|
23
|
+
*/
|
24
|
+
expirationTime?: Date | undefined
|
25
|
+
/**
|
26
|
+
* Time when the message was generated, typically the current time.
|
27
|
+
*/
|
28
|
+
issuedAt?: Date | undefined
|
29
|
+
/**
|
30
|
+
* A random string typically chosen by the relying party and used to prevent replay attacks.
|
31
|
+
*/
|
32
|
+
nonce: string
|
33
|
+
/**
|
34
|
+
* Time when the signed authentication message will become valid.
|
35
|
+
*/
|
36
|
+
notBefore?: Date | undefined
|
37
|
+
/**
|
38
|
+
* A system-specific identifier that may be used to uniquely refer to the sign-in request.
|
39
|
+
*/
|
40
|
+
requestId?: string | undefined
|
41
|
+
/**
|
42
|
+
* A list of information or references to information the user wishes to have resolved as part of authentication by the relying party.
|
43
|
+
*/
|
44
|
+
resources?: string[] | undefined
|
45
|
+
/**
|
46
|
+
* [RFC 3986](https://www.rfc-editor.org/rfc/rfc3986#section-3.1) URI scheme of the origin of the request.
|
47
|
+
*/
|
48
|
+
scheme?: string | undefined
|
49
|
+
/**
|
50
|
+
* A human-readable ASCII assertion that the user will sign.
|
51
|
+
*/
|
52
|
+
statement?: string | undefined
|
53
|
+
/**
|
54
|
+
* [RFC 3986](https://www.rfc-editor.org/rfc/rfc3986) URI referring to the resource that is the subject of the signing (as in the subject of a claim).
|
55
|
+
*/
|
56
|
+
uri: string
|
57
|
+
/**
|
58
|
+
* The current version of the SIWE Message.
|
59
|
+
*/
|
60
|
+
version: '1'
|
61
|
+
}
|
@@ -0,0 +1,51 @@
|
|
1
|
+
export function isUri(value: string) {
|
2
|
+
// based on https://github.com/ogt/valid-url
|
3
|
+
|
4
|
+
// check for illegal characters
|
5
|
+
if (/[^a-z0-9\:\/\?\#\[\]\@\!\$\&\'\(\)\*\+\,\;\=\.\-\_\~\%]/i.test(value))
|
6
|
+
return false
|
7
|
+
|
8
|
+
// check for hex escapes that aren't complete
|
9
|
+
if (/%[^0-9a-f]/i.test(value)) return false
|
10
|
+
if (/%[0-9a-f](:?[^0-9a-f]|$)/i.test(value)) return false
|
11
|
+
|
12
|
+
// from RFC 3986
|
13
|
+
const splitted = splitUri(value)
|
14
|
+
const scheme = splitted[1]
|
15
|
+
const authority = splitted[2]
|
16
|
+
const path = splitted[3]
|
17
|
+
const query = splitted[4]
|
18
|
+
const fragment = splitted[5]
|
19
|
+
|
20
|
+
// scheme and path are required, though the path can be empty
|
21
|
+
if (!(scheme?.length && path.length >= 0)) return false
|
22
|
+
|
23
|
+
// if authority is present, the path must be empty or begin with a /
|
24
|
+
if (authority?.length) {
|
25
|
+
if (!(path.length === 0 || /^\//.test(path))) return false
|
26
|
+
} else {
|
27
|
+
// if authority is not present, the path must not start with //
|
28
|
+
if (/^\/\//.test(path)) return false
|
29
|
+
}
|
30
|
+
|
31
|
+
// scheme must begin with a letter, then consist of letters, digits, +, ., or -
|
32
|
+
if (!/^[a-z][a-z0-9\+\-\.]*$/.test(scheme.toLowerCase())) return false
|
33
|
+
|
34
|
+
let out = ''
|
35
|
+
// re-assemble the URL per section 5.3 in RFC 3986
|
36
|
+
out += `${scheme}:`
|
37
|
+
if (authority?.length) out += `//${authority}`
|
38
|
+
|
39
|
+
out += path
|
40
|
+
|
41
|
+
if (query?.length) out += `?${query}`
|
42
|
+
if (fragment?.length) out += `#${fragment}`
|
43
|
+
|
44
|
+
return out
|
45
|
+
}
|
46
|
+
|
47
|
+
function splitUri(value: string) {
|
48
|
+
return value.match(
|
49
|
+
/(?:([^:\/?#]+):)?(?:\/\/([^\/?#]*))?([^?#]*)(?:\?([^#]*))?(?:#(.*))?/,
|
50
|
+
)!
|
51
|
+
}
|
@@ -0,0 +1,70 @@
|
|
1
|
+
import type { Address } from 'abitype'
|
2
|
+
|
3
|
+
import type { ExactPartial } from '../../types/utils.js'
|
4
|
+
import { isAddressEqual } from '../address/isAddressEqual.js'
|
5
|
+
import type { SiweMessage } from './types.js'
|
6
|
+
|
7
|
+
export type ValidateSiweMessageParameters = {
|
8
|
+
/**
|
9
|
+
* Ethereum address to check against.
|
10
|
+
*/
|
11
|
+
address?: Address | undefined
|
12
|
+
/**
|
13
|
+
* [RFC 3986](https://www.rfc-editor.org/rfc/rfc3986) authority to check against.
|
14
|
+
*/
|
15
|
+
domain?: string | undefined
|
16
|
+
/**
|
17
|
+
* EIP-4361 message fields.
|
18
|
+
*/
|
19
|
+
message: ExactPartial<SiweMessage>
|
20
|
+
/**
|
21
|
+
* Random string to check against.
|
22
|
+
*/
|
23
|
+
nonce?: string | undefined
|
24
|
+
/**
|
25
|
+
* [RFC 3986](https://www.rfc-editor.org/rfc/rfc3986#section-3.1) URI scheme to check against.
|
26
|
+
*/
|
27
|
+
scheme?: string | undefined
|
28
|
+
/**
|
29
|
+
* Current time to check optional `expirationTime` and `notBefore` fields.
|
30
|
+
*
|
31
|
+
* @default new Date()
|
32
|
+
*/
|
33
|
+
time?: Date | undefined
|
34
|
+
}
|
35
|
+
|
36
|
+
export type ValidateSiweMessageReturnType = boolean
|
37
|
+
|
38
|
+
/**
|
39
|
+
* @description Validates EIP-4361 message.
|
40
|
+
*
|
41
|
+
* @see https://eips.ethereum.org/EIPS/eip-4361
|
42
|
+
*/
|
43
|
+
export function validateSiweMessage(
|
44
|
+
parameters: ValidateSiweMessageParameters,
|
45
|
+
): ValidateSiweMessageReturnType {
|
46
|
+
const {
|
47
|
+
address,
|
48
|
+
domain,
|
49
|
+
message,
|
50
|
+
nonce,
|
51
|
+
scheme,
|
52
|
+
time = new Date(),
|
53
|
+
} = parameters
|
54
|
+
|
55
|
+
if (domain && message.domain !== domain) return false
|
56
|
+
if (nonce && message.nonce !== nonce) return false
|
57
|
+
if (scheme && message.scheme !== scheme) return false
|
58
|
+
|
59
|
+
if (message.expirationTime && time >= message.expirationTime) return false
|
60
|
+
if (message.notBefore && time < message.notBefore) return false
|
61
|
+
|
62
|
+
try {
|
63
|
+
if (!message.address) return false
|
64
|
+
if (address && !isAddressEqual(message.address, address)) return false
|
65
|
+
} catch {
|
66
|
+
return false
|
67
|
+
}
|
68
|
+
|
69
|
+
return true
|
70
|
+
}
|