samlesa 3.4.2 → 3.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,11 +5,13 @@
5
5
  */
6
6
  import { checkStatus } from "./flow.js";
7
7
  import { ParserType, StatusCode, wording } from './urn.js';
8
+ import * as crypto from "node:crypto";
8
9
  import libsaml from './libsaml.js';
9
10
  import libsamlSoap from './libsamlSoap.js';
10
11
  import utility, { get } from './utility.js';
11
12
  import { fileURLToPath } from "node:url";
12
13
  import { randomUUID } from 'node:crypto';
14
+ import postBinding from './binding-post.js';
13
15
  import { artifactResolveFields, extract, loginRequestFields, loginResponseFields, logoutRequestFields, logoutResponseFields } from "./extractor.js";
14
16
  import { verifyTime } from "./validator.js";
15
17
  import { sendArtifactResolve } from "./soap.js";
@@ -137,141 +139,111 @@ function soapLoginRequest(referenceTagXPath, entity, customTagReplacement) {
137
139
  throw new Error('ERR_GENERATE_POST_LOGIN_REQUEST_MISSING_METADATA');
138
140
  }
139
141
  /**
140
- * @desc Generate a base64 encoded login response
141
- * @param {object} requestInfo corresponding request, used to obtain the id
142
- * @param {object} entity object includes both idp and sp
143
- * @param {object} user current logged user (e.g. req.user)
144
- * @param {function} customTagReplacement used when developers have their own login response template
145
- * @param {boolean} encryptThenSign whether or not to encrypt then sign first (if signing). Defaults to sign-then-encrypt
146
- * @param AttributeStatement
142
+ * Generates a SAML 2.0 compliant Artifact ID.
143
+ * Format: [4-byte source ID][2-byte endpoint index][20-byte sequence value]
144
+ * @param issuerId The entity ID of the issuing party (IdP).
145
+ * @param endpointIndex The index of the destination endpoint (default is '0004' for Artifact Resolution Service).
146
+ * @returns The Base64 encoded Artifact ID string.
147
147
  */
148
- async function soapLoginResponse(requestInfo = {}, entity, user = {}, customTagReplacement, encryptThenSign = false, AttributeStatement = []) {
149
- const idpSetting = entity.idp.entitySetting;
150
- const spSetting = entity.sp.entitySetting;
151
- const id = idpSetting.generateID();
148
+ /**
149
+ * 生成符合 SAML 2.0 规范的 Artifact
150
+ * 结构: [TypeCode: 2 bytes] + [EndpointIndex: 2 bytes] + [SourceID: 20 bytes] + [MessageHandle: 20 bytes]
151
+ */
152
+ function generateArtifactId(issuerId, endpointIndex = 1) {
153
+ // 1. SourceID: 20 bytes SHA-1
154
+ const issuerHash = crypto.createHash('sha1').update(issuerId).digest();
155
+ const sourceId = issuerHash.subarray(0, 20); // 必须 20 字节
156
+ // 2. TypeCode: 0x0004
157
+ const typeCode = Buffer.from([0x00, 0x04]);
158
+ // 3. EndpointIndex: 2 bytes Big Endian
159
+ const indexBuffer = Buffer.alloc(2);
160
+ indexBuffer.writeUInt16BE(endpointIndex, 0);
161
+ // 4. MessageHandle: 20 random bytes
162
+ const messageHandle = crypto.randomBytes(20);
163
+ // 5. Concat: 2 + 2 + 20 + 20 = 44 bytes
164
+ const artifactBytes = Buffer.concat([typeCode, indexBuffer, sourceId, messageHandle]);
165
+ // 6. Base64
166
+ return artifactBytes.toString('base64');
167
+ }
168
+ // Initial response that sends only the artifact ID
169
+ async function soapLoginResponse(params) {
170
+ const { entity } = params;
152
171
  const metadata = {
153
172
  idp: entity.idp.entityMeta,
154
173
  sp: entity.sp.entityMeta,
155
174
  };
156
- const nameIDFormat = idpSetting.nameIDFormat;
157
- const selectedNameIDFormat = Array.isArray(nameIDFormat) ? nameIDFormat[0] : nameIDFormat;
158
- if (metadata && metadata.idp && metadata.sp) {
159
- const base = metadata.sp.getAssertionConsumerService(binding.post);
160
- let rawSamlResponse;
161
- const nowTime = new Date();
162
- const spEntityID = metadata.sp.getEntityID();
163
- const oneMinutesLaterTime = new Date(nowTime.getTime());
164
- oneMinutesLaterTime.setMinutes(oneMinutesLaterTime.getMinutes() + 5);
165
- const OneMinutesLater = oneMinutesLaterTime.toISOString();
166
- const now = nowTime.toISOString();
167
- const acl = metadata.sp.getAssertionConsumerService(binding.post);
168
- const sessionIndex = 'session' + idpSetting.generateID(); // 这个是当前系统的会话索引,用于单点注销
169
- const tenHoursLaterTime = new Date(nowTime.getTime());
170
- tenHoursLaterTime.setHours(tenHoursLaterTime.getHours() + 10);
171
- const tenHoursLater = tenHoursLaterTime.toISOString();
172
- const tvalue = {
173
- ID: id,
174
- AssertionID: idpSetting.generateID(),
175
- Destination: base,
176
- Audience: spEntityID,
177
- EntityID: spEntityID,
178
- SubjectRecipient: acl,
179
- Issuer: metadata.idp.getEntityID(),
180
- IssueInstant: now,
181
- AssertionConsumerServiceURL: acl,
182
- StatusCode: StatusCode.Success,
183
- // can be customized
184
- ConditionsNotBefore: now,
185
- ConditionsNotOnOrAfter: OneMinutesLater,
186
- SubjectConfirmationDataNotOnOrAfter: OneMinutesLater,
187
- NameIDFormat: selectedNameIDFormat,
188
- NameID: user?.NameID || '',
189
- InResponseTo: get(requestInfo, 'extract.request.id', ''),
190
- AuthnStatement: `<saml:AuthnStatement AuthnInstant="${now}" SessionNotOnOrAfter="${tenHoursLater}" SessionIndex="${sessionIndex}"><saml:AuthnContext><saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml:AuthnContextClassRef></saml:AuthnContext></saml:AuthnStatement>`,
191
- AttributeStatement: libsaml.attributeStatementBuilder(AttributeStatement),
192
- };
193
- if (idpSetting.loginResponseTemplate && customTagReplacement) {
194
- const template = customTagReplacement(idpSetting.loginResponseTemplate.context);
195
- rawSamlResponse = get(template, 'context', null);
196
- }
197
- else {
198
- if (requestInfo !== null) {
199
- tvalue.InResponseTo = requestInfo?.extract?.request?.id ?? '';
200
- }
201
- rawSamlResponse = libsaml.replaceTagsByValue(libsaml.defaultLoginResponseTemplate.context, tvalue);
202
- }
203
- const { privateKey, privateKeyPass, requestSignatureAlgorithm: signatureAlgorithm } = idpSetting;
204
- const config = {
205
- privateKey,
206
- privateKeyPass,
207
- signatureAlgorithm,
208
- signingCert: metadata.idp.getX509Certificate('signing'),
209
- isBase64Output: false,
210
- };
211
- // step: sign assertion ? -> encrypted ? -> sign message ?
212
- if (metadata.sp.isWantAssertionsSigned()) {
213
- rawSamlResponse = libsaml.constructSAMLSignature({
214
- ...config,
215
- rawSamlMessage: rawSamlResponse,
216
- transformationAlgorithms: spSetting.transformationAlgorithms,
217
- referenceTagXPath: "/*[local-name(.)='Response']/*[local-name(.)='Assertion']",
218
- signatureConfig: {
219
- prefix: 'ds',
220
- location: {
221
- reference: "/*[local-name(.)='Response']/*[local-name(.)='Assertion']/*[local-name(.)='Issuer']",
222
- action: 'after'
223
- },
224
- },
225
- });
226
- }
227
- // console.debug('after assertion signed', rawSamlResponse);
228
- // SAML response must be signed sign message first, then encrypt
229
- if (!encryptThenSign && (spSetting.wantMessageSigned || !metadata.sp.isWantAssertionsSigned())) {
230
- // console.debug('sign then encrypt and sign entire message');
231
- // @ts-ignore
232
- rawSamlResponse = libsaml.constructSAMLSignature({
233
- ...config,
234
- rawSamlMessage: rawSamlResponse,
235
- isMessageSigned: true,
236
- transformationAlgorithms: spSetting.transformationAlgorithms,
237
- signatureConfig: spSetting.signatureConfig || {
238
- prefix: 'ds',
239
- location: { reference: "/*[local-name(.)='Response']/*[local-name(.)='Issuer']", action: 'after' },
240
- },
241
- });
242
- }
243
- // console.debug('after message signed', rawSamlResponse);
244
- if (idpSetting.isAssertionEncrypted) {
245
- // console.debug('idp is configured to do encryption');
246
- const context = await libsaml.encryptAssertion(entity.idp, entity.sp, rawSamlResponse);
247
- if (encryptThenSign) {
248
- //need to decode it
249
- rawSamlResponse = utility.base64Decode(context);
250
- }
251
- else {
252
- return Promise.resolve({ id, context });
253
- }
254
- }
255
- //sign after encrypting
256
- if (encryptThenSign && (spSetting.wantMessageSigned || !metadata.sp.isWantAssertionsSigned())) {
257
- // @ts-ignore
258
- rawSamlResponse = libsaml.constructSAMLSignature({
259
- ...config,
260
- rawSamlMessage: rawSamlResponse,
261
- isMessageSigned: true,
262
- transformationAlgorithms: spSetting.transformationAlgorithms,
263
- signatureConfig: spSetting.signatureConfig || {
264
- prefix: 'ds',
265
- location: { reference: "/*[local-name(.)='Response']/*[local-name(.)='Issuer']", action: 'after' },
266
- },
267
- });
268
- }
269
- return Promise.resolve({
270
- id,
271
- context: utility.base64Encode(rawSamlResponse),
272
- });
175
+ if (!metadata.idp || !metadata.sp) {
176
+ throw new Error('ERR_GENERATE_ARTIFACT_MISSING_METADATA');
273
177
  }
274
- throw new Error('ERR_GENERATE_POST_LOGIN_RESPONSE_MISSING_METADATA');
178
+ // 1. Generate the base SAML Response (Base64 encoded)
179
+ const samlResponseResult = await postBinding.base64LoginResponse(params);
180
+ const samlResponseXml = utility.base64Decode(samlResponseResult.context);
181
+ console.log(samlResponseXml);
182
+ console.log("我日你妈==");
183
+ // 2. Generate the SAML 2.0 Artifact ID
184
+ const artifactId = generateArtifactId(metadata.idp.getEntityID());
185
+ // 3. Cache the SAML Response XML using the artifactId as the key
186
+ // TODO: Implement your Redis caching logic here
187
+ // Example:
188
+ // const redisExpirySeconds = 300; // e.g., expire after 5 minutes
189
+ // await redis.setex(`artifact_cache:${artifactId}`, redisExpirySeconds, samlResponseXml);
190
+ // --- INSERT YOUR REDIS LOGIC HERE ---
191
+ // Example: await cacheInRedis(artifactId, samlResponseXml);
192
+ // 4. Prepare config for SOAP signing (reusing IDP settings)
193
+ const idpSetting = entity.idp.entitySetting;
194
+ const spSetting = entity.sp.entitySetting;
195
+ const config = {
196
+ privateKey: idpSetting.privateKey,
197
+ privateKeyPass: idpSetting.privateKeyPass,
198
+ signatureAlgorithm: idpSetting.requestSignatureAlgorithm,
199
+ signingCert: metadata.idp.getX509Certificate('signing'),
200
+ isBase64Output: false,
201
+ };
202
+ // 5. Construct the SOAP Envelope containing the Artifact ID
203
+ // This is CORRECT - in the initial response we only send the artifact ID
204
+ // The actual SAML response will be retrieved by the SP using the artifact resolution service
205
+ const soapBodyContent = `<samlp:ArtifactResponse
206
+ xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
207
+ xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
208
+ ID="${samlResponseResult.id}_artifact_resp"
209
+ InResponseTo="${params.requestInfo?.extract?.request?.id || ''}"
210
+ Version="2.0"
211
+ IssueInstant="${new Date().toISOString()}">
212
+ <saml2:Issuer>${metadata.idp.getEntityID()}</saml2:Issuer>
213
+ <samlp:Status>
214
+ <samlp:StatusCode Value="${StatusCode.Success}"/>
215
+ </samlp:Status>
216
+ ${samlResponseXml}
217
+ </samlp:ArtifactResponse>`;
218
+ const soapEnvelope = `
219
+ <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
220
+ <soap:Header/>
221
+ <soap:Body>${soapBodyContent}</soap:Body>
222
+ </soap:Envelope>`;
223
+ // 6. Sign the SOAP Envelope
224
+ // Reference the Body element for signature
225
+ // @ts-ignore
226
+ // @ts-ignore
227
+ const signedSoapEnvelope = libsaml.constructSAMLSignature({
228
+ ...config,
229
+ rawSamlMessage: soapEnvelope,
230
+ transformationAlgorithms: spSetting.transformationAlgorithms,
231
+ isMessageSigned: true,
232
+ referenceTagXPath: "/*[local-name(.)='Envelope']/*[local-name(.)='Body']/*[local-name(.)='ArtifactResponse']",
233
+ signatureConfig: {
234
+ prefix: 'ds',
235
+ location: {
236
+ // Place signature inside the ArtifactResponse element, before the Status element
237
+ reference: "/*[local-name(.)='Envelope']/*[local-name(.)='Body']/*[local-name(.)='ArtifactResponse']/*[local-name(.)='Status']",
238
+ action: 'before'
239
+ },
240
+ },
241
+ });
242
+ // 7. Return the Artifact ID and the Base64 encoded signed SOAP message
243
+ return {
244
+ id: artifactId, // This is the key you'll need for resolving the artifact later
245
+ context: utility.base64Encode(signedSoapEnvelope), // The SOAP message to send back
246
+ };
275
247
  }
276
248
  async function parseLoginRequestResolve(params) {
277
249
  let { idp, sp, xml, } = params;
@@ -10,6 +10,7 @@ import { namespace } from './urn.js';
10
10
  import postBinding from './binding-post.js';
11
11
  import redirectBinding from './binding-redirect.js';
12
12
  import simpleSignBinding from './binding-simplesign.js';
13
+ import artifactBinding from './binding-artifact.js';
13
14
  import { flow } from './flow.js';
14
15
  /**
15
16
  * Identity provider can be configured using either metadata importing or idpSetting
@@ -36,7 +37,7 @@ export class IdentityProvider extends Entity {
36
37
  * @param params
37
38
  */
38
39
  async createLoginResponse(params) {
39
- const bindType = params?.binding ?? 'post';
40
+ const bindType = (params?.binding ?? 'post');
40
41
  const { sp, requestInfo = {}, user = {}, customTagReplacement, encryptThenSign = false, relayState = '', AttributeStatement = [], idpInit = false, } = params;
41
42
  const protocol = namespace.binding[bindType];
42
43
  // can support post, redirect and post simple sign bindings for login response
@@ -66,6 +67,19 @@ export class IdentityProvider extends Entity {
66
67
  idp: this,
67
68
  sp,
68
69
  }, user, relayState, customTagReplacement, AttributeStatement);
70
+ case namespace.binding.artifact:
71
+ return artifactBinding.soapLoginResponse({
72
+ requestInfo,
73
+ entity: {
74
+ idp: this,
75
+ sp,
76
+ },
77
+ user,
78
+ customTagReplacement,
79
+ encryptThenSign,
80
+ AttributeStatement,
81
+ idpInit,
82
+ });
69
83
  default:
70
84
  throw new Error('ERR_CREATE_RESPONSE_UNDEFINED_BINDING');
71
85
  }
@@ -117,10 +117,27 @@ export const loginRequestFields = [
117
117
  { key: 'signature', localPath: ['AuthnRequest', 'Signature'], attributes: [], context: true }
118
118
  ];
119
119
  export const artifactResolveFields = [
120
- { key: 'request', localPath: ['ArtifactResolve'], attributes: ['ID', 'IssueInstant', 'Version'] },
121
- { key: 'issuer', localPath: ['ArtifactResolve', 'Issuer'], attributes: [] },
122
- { key: 'Artifact', localPath: ['ArtifactResolve', 'Artifact'], attributes: [] },
123
- { key: 'signature', localPath: ['ArtifactResolve', 'Signature'], attributes: [], context: true },
120
+ {
121
+ key: 'request',
122
+ localPath: ['Envelope', 'Body', 'ArtifactResolve'],
123
+ attributes: ['ID', 'IssueInstant', 'Version', 'Destination']
124
+ },
125
+ {
126
+ key: 'issuer',
127
+ localPath: ['Envelope', 'Body', 'ArtifactResolve', 'Issuer'],
128
+ attributes: []
129
+ },
130
+ {
131
+ key: 'artifact',
132
+ localPath: ['Envelope', 'Body', 'ArtifactResolve', 'Artifact'],
133
+ attributes: []
134
+ },
135
+ {
136
+ key: 'signature',
137
+ localPath: ['Envelope', 'Body', 'ArtifactResolve', 'Signature'],
138
+ attributes: [],
139
+ context: true
140
+ },
124
141
  ];
125
142
  export const artifactResponseFields = [
126
143
  { key: 'request', localPath: ['Envelope', 'Body', 'ArtifactResolve'], attributes: ['ID', 'IssueInstant', 'Version'] },
@@ -1487,6 +1504,9 @@ export function extractSp(context) {
1487
1504
  export function extractAuthRequest(context) {
1488
1505
  return extract(context, loginRequestFields);
1489
1506
  }
1490
- export function extractResponse(context, ass) {
1507
+ export function extractResponse(context) {
1491
1508
  return extractSpToll(context, loginResponseFieldsFullList);
1492
1509
  }
1510
+ export function extractArtifactResolve(context) {
1511
+ return extract(context, artifactResolveFields);
1512
+ }
@@ -5,7 +5,8 @@ import { fileURLToPath } from 'node:url';
5
5
  import { DOMParser } from '@xmldom/xmldom';
6
6
  const __filename = fileURLToPath(import.meta.url);
7
7
  const __dirname = path.dirname(__filename);
8
- let normal = [
8
+ // 定义各个场景所需的 schema 文件列表(保持不变)
9
+ const normalSchemas = [
9
10
  'saml-schema-protocol-2.0.xsd',
10
11
  'saml-schema-assertion-2.0.xsd',
11
12
  'xmldsig-core-schema.xsd',
@@ -15,33 +16,31 @@ let normal = [
15
16
  'saml-schema-ecp-2.0.xsd',
16
17
  'saml-schema-dce-2.0.xsd'
17
18
  ];
18
- let soapSchema = [
19
+ const soapSchemas = [
19
20
  'soap-envelope.xsd',
20
21
  'xml.xsd',
21
- // 2. SOAP核心模式(所有SOAP消息的基础)
22
- // 3. XML签名模式(SAML签名的前置依赖)
23
22
  'xmldsig-core-schema.xsd',
24
- // 4. XML加密模式(SAML断言加密的前置依赖)
25
23
  'xenc-schema.xsd',
26
24
  'xenc-schema-11.xsd',
27
- // 5. SAML核心模式(最基础的SAML组件)
28
- 'saml-schema-assertion-2.0.xsd', // 断言定义
29
- // 6. SAML协议模式(依赖断言模式)
25
+ 'saml-schema-assertion-2.0.xsd',
30
26
  'saml-schema-protocol-2.0.xsd',
31
- // 7. SAML扩展模式(依赖核心模式)
32
- 'saml-schema-metadata-2.0.xsd', // 元数据
33
- 'saml-schema-ecp-2.0.xsd', // ECP扩展
34
- 'saml-schema-dce-2.0.xsd' // DCE扩展
27
+ 'saml-schema-metadata-2.0.xsd',
28
+ 'saml-schema-ecp-2.0.xsd',
29
+ 'saml-schema-dce-2.0.xsd'
35
30
  ];
36
- let meta = [
37
- 'saml-schema-metadata-2.0.xsd', // 元数据
31
+ const metadataSchemas = [
32
+ 'saml-schema-metadata-2.0.xsd',
38
33
  'xml.xsd',
39
34
  'saml-schema-assertion-2.0.xsd',
40
35
  'xmldsig-core-schema.xsd',
41
36
  'xenc-schema.xsd',
42
- 'xenc-schema-11.xsd',
37
+ 'xenc-schema-11.xsd'
43
38
  ];
44
- let schemas = normal;
39
+ /**
40
+ * 检测 XML 字符串中是否存在 XXE 攻击指示器
41
+ * @param samlString 待检测的 XML 字符串
42
+ * @returns 如果存在可疑模式则返回匹配详情,否则返回 null
43
+ */
45
44
  function detectXXEIndicators(samlString) {
46
45
  const xxePatterns = [
47
46
  /<!DOCTYPE\s[^>]*>/i,
@@ -64,94 +63,112 @@ function detectXXEIndicators(samlString) {
64
63
  });
65
64
  return Object.keys(matches).length > 0 ? matches : null;
66
65
  }
66
+ /**
67
+ * 加载指定的 schema 文件内容
68
+ * @param schemaNames 文件名数组
69
+ * @returns 包含 fileName 和 contents 的对象数组
70
+ */
71
+ async function loadSchemas(schemaNames) {
72
+ const schemaPath = path.resolve(__dirname, 'schema');
73
+ return Promise.all(schemaNames.map(async (file) => ({
74
+ fileName: file,
75
+ contents: await fs.promises.readFile(`${schemaPath}/${file}`, 'utf-8')
76
+ })));
77
+ }
78
+ /**
79
+ * 验证 SAML 消息(普通或 SOAP)
80
+ * @param xml XML 字符串
81
+ * @param isSoap 是否为 SOAP 消息,默认 false
82
+ * @returns true 表示验证通过,否则抛出错误
83
+ * @throws 当检测到 XXE 或验证失败时抛出错误
84
+ */
67
85
  export const validate = async (xml, isSoap = false) => {
86
+ // 检测 XXE 攻击
68
87
  const indicators = detectXXEIndicators(xml);
69
88
  if (indicators) {
70
89
  throw new Error('ERR_EXCEPTION_VALIDATE_XML');
71
90
  }
72
- schemas = isSoap ? soapSchema : normal;
73
- const schemaPath = path.resolve(__dirname, 'schema');
74
- const [xmlParse, ...preload] = await Promise.all(schemas.map(async (file) => ({
75
- fileName: file,
76
- contents: await fs.promises.readFile(`${schemaPath}/${file}`, 'utf-8')
77
- })));
91
+ // 根据类型选择对应的 schema 列表(避免全局变量并发问题)
92
+ const schemaList = isSoap ? soapSchemas : normalSchemas;
93
+ const schemas = await loadSchemas(schemaList);
78
94
  try {
79
95
  const validationResult = await validateXML({
80
- xml: [
81
- {
82
- fileName: 'content.xml',
83
- contents: xml,
84
- },
85
- ],
96
+ xml: [{ fileName: 'content.xml', contents: xml }],
86
97
  extension: 'schema',
87
- schema: [xmlParse],
88
- preload: [xmlParse, ...preload],
98
+ schema: [schemas[0]], // 第一个 schema 作为主入口
99
+ preload: [schemas[0], ...schemas.slice(1)], // 其余作为预加载
89
100
  });
90
101
  if (validationResult.valid) {
91
102
  return true;
92
103
  }
104
+ // 验证失败,抛出错误对象
93
105
  throw validationResult.errors;
94
106
  }
95
107
  catch (error) {
96
- throw new Error('ERR_EXCEPTION_VALIDATE_XML');
108
+ // 保留原始错误信息
109
+ throw error;
97
110
  }
98
111
  };
112
+ /**
113
+ * 验证 SAML 元数据,并可选择解析元数据类型
114
+ * @param xml XML 字符串
115
+ * @param isParse 是否解析并返回元数据类型,默认 false
116
+ * @returns 验证通过时:若 isParse 为 true 返回 { isValid: true, metadataType: string },否则返回 true;
117
+ * 验证失败时返回 Error 对象(保持原行为)
118
+ */
99
119
  export const validateMetadata = async (xml, isParse = false) => {
120
+ // 检测 XXE 攻击
100
121
  const indicators = detectXXEIndicators(xml);
101
122
  if (indicators) {
102
123
  throw new Error('ERR_EXCEPTION_VALIDATE_XML');
103
124
  }
104
- schemas = meta;
105
- const schemaPath = path.resolve(__dirname, 'schema');
106
- const [xmlParse, ...preload] = await Promise.all(schemas.map(async (file) => ({
107
- fileName: file,
108
- contents: await fs.promises.readFile(`${schemaPath}/${file}`, 'utf-8')
109
- })));
125
+ const schemas = await loadSchemas(metadataSchemas);
110
126
  try {
127
+ // @ts-ignore
111
128
  const validationResult = await validateXML({
112
- xml: [
113
- {
114
- fileName: 'content.xml',
115
- contents: xml,
116
- },
117
- ],
129
+ xml: [{ fileName: 'content.xml', contents: xml }],
118
130
  extension: 'schema',
119
- schema: [xmlParse],
120
- preload: [xmlParse, ...preload],
131
+ schema: [schemas[0]],
132
+ preload: [schemas[0], ...schemas.slice(1)],
121
133
  });
122
134
  if (validationResult.valid) {
123
135
  if (isParse) {
124
- // 解析 XML 为 DOM 对象
136
+ // 解析 XML 并确定元数据类型
125
137
  const parser = new DOMParser();
126
138
  const xmlDoc = parser.parseFromString(xml, 'text/xml');
127
- // 检查 IdP 和 SP 描述符元素
139
+ // 检查解析错误(防御性编程)
140
+ const parserError = xmlDoc.getElementsByTagName('parsererror');
141
+ if (parserError.length > 0) {
142
+ // 解析失败,视为无效 XML,返回错误对象(与原逻辑一致)
143
+ return new Error('XML parsing failed');
144
+ }
128
145
  const idpDescriptor = xmlDoc.getElementsByTagNameNS('urn:oasis:names:tc:SAML:2.0:metadata', 'IDPSSODescriptor');
129
146
  const spDescriptor = xmlDoc.getElementsByTagNameNS('urn:oasis:names:tc:SAML:2.0:metadata', 'SPSSODescriptor');
130
- // 判断元数据类型
131
147
  let metadataType;
132
148
  if (idpDescriptor.length > 0 && spDescriptor.length > 0) {
133
- metadataType = 'both'; // 同时包含 IdP 和 SP
149
+ metadataType = 'both';
134
150
  }
135
151
  else if (idpDescriptor.length > 0) {
136
- metadataType = 'IdP'; // 身份提供者
152
+ metadataType = 'IdP';
137
153
  }
138
154
  else if (spDescriptor.length > 0) {
139
- metadataType = 'SP'; // 服务提供者
155
+ metadataType = 'SP';
140
156
  }
141
157
  else {
142
- metadataType = 'unknown'; // 无法确定
158
+ metadataType = 'unknown';
143
159
  }
144
- // 返回验证结果和元数据类型
145
160
  return {
146
161
  isValid: true,
147
- metadataType: metadataType
162
+ metadataType
148
163
  };
149
164
  }
150
165
  return true;
151
166
  }
152
- throw validationResult.errors;
167
+ // 验证失败,返回错误对象(保持原行为)
168
+ return validationResult.errors;
153
169
  }
154
170
  catch (error) {
155
- return error;
171
+ // 捕获其他异常(如文件读取失败)并返回错误对象
172
+ return error instanceof Error ? error : new Error(String(error));
156
173
  }
157
174
  };
package/build/src/urn.js CHANGED
@@ -10,6 +10,16 @@ export var BindingNamespace;
10
10
  BindingNamespace["SimpleSign"] = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign";
11
11
  BindingNamespace["Artifact"] = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact";
12
12
  })(BindingNamespace || (BindingNamespace = {}));
13
+ export const NamespaceBindingMap = {
14
+ 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect': 'redirect',
15
+ 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST': 'post',
16
+ 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign': 'simplesign',
17
+ 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact': 'artifact'
18
+ };
19
+ // 可选:添加反向查找函数
20
+ function getBindingName(uri) {
21
+ return NamespaceBindingMap[uri];
22
+ }
13
23
  export var MessageSignatureOrder;
14
24
  (function (MessageSignatureOrder) {
15
25
  MessageSignatureOrder["STE"] = "sign-then-encrypt";
@@ -51,6 +61,12 @@ const namespace = {
51
61
  artifact: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact',
52
62
  soap: 'urn:oasis:names:tc:SAML:2.0:bindings:SOAP',
53
63
  },
64
+ bindMap: {
65
+ 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect': 'redirect',
66
+ 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST': 'post',
67
+ 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign': 'simplesign',
68
+ 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact': 'artifact'
69
+ },
54
70
  names: {
55
71
  protocol: 'urn:oasis:names:tc:SAML:2.0:protocol',
56
72
  assertion: 'urn:oasis:names:tc:SAML:2.0:assertion',
@@ -306,4 +322,4 @@ const elementsOrder = {
306
322
  onelogin: ['KeyDescriptor', 'NameIDFormat', 'ArtifactResolutionService', 'SingleLogoutService', 'AssertionConsumerService', 'AttributeConsumingService'],
307
323
  shibboleth: ['KeyDescriptor', 'ArtifactResolutionService', 'SingleLogoutService', 'NameIDFormat', 'AssertionConsumerService', 'AttributeConsumingService',],
308
324
  };
309
- export { namespace, tags, algorithms, wording, elementsOrder, messageConfigurations };
325
+ export { namespace, tags, algorithms, wording, elementsOrder, messageConfigurations, getBindingName };
package/package.json CHANGED
@@ -1,75 +1,75 @@
1
- {
2
- "name": "samlesa",
3
- "version": "3.4.2",
4
- "description": "High-level API for Single Sign On (SAML 2.0) baseed on samlify ",
5
- "main": "build/index.js",
6
- "keywords": [
7
- "nodejs",
8
- "saml2",
9
- "sso",
10
- "slo",
11
- "metadata"
12
- ],
13
- "type": "module",
14
- "typings": "types/index.d.ts",
15
- "scripts": {
16
- "build": "tsc && copyfiles -u 1 src/schema/**/* build/src",
17
- "docs": "docsify serve -o docs",
18
- "lint": "tslint -p .",
19
- "lint:fix": "tslint -p . --fix",
20
- "test": "vitest",
21
- "test:watch": "vitest --watch",
22
- "test:coverage": "vitest run --coverage",
23
- "hooks:postinstall": "mklink /J .git\\hooks\\pre-commit .pre-commit.sh || copy .pre-commit.sh .git\\hooks\\pre-commit"
24
- },
25
- "exports": {
26
- ".": {
27
- "types": "./types/index.d.ts",
28
- "import": "./build/index.js"
29
- }
30
- },
31
- "files": [
32
- "build",
33
- "types"
34
- ],
35
- "contributors": [
36
- "Veclea <vemocle@gmail.com>"
37
- ],
38
- "author": "Veclea",
39
- "repository": {
40
- "url": "https://github.com/Veclea/samlify.git",
41
- "type": "git"
42
- },
43
- "license": "MIT",
44
- "dependencies": {
45
- "@xmldom/xmldom": "^0.9.8",
46
- "axios": "^1.13.5",
47
- "camelcase": "^9.0.0",
48
- "cross-env": "^10.1.0",
49
- "iconv-lite": "^0.7.2",
50
- "ts-node": "^10.9.2",
51
- "vite-tsconfig-paths": "^6.1.1",
52
- "xml": "^1.0.1",
53
- "xml-crypto": "^6.1.2",
54
- "xml-crypto-next": "^7.0.4",
55
- "xml-encryption-next": "^4.6.0",
56
- "xml-escape": "^1.1.0",
57
- "xml2js": "^0.6.2",
58
- "xmllint-wasm": "^5.1.0",
59
- "xpath": "^0.0.34"
60
- },
61
- "devDependencies": {
62
- "@types/node": "^25.3.2",
63
- "@types/pako": "2.0.4",
64
- "@types/uuid": "11.0.0",
65
- "@vitest/coverage-istanbul": "^4.0.18",
66
- "@vitest/coverage-v8": "4.0.18",
67
- "copyfiles": "^2.4.1",
68
- "coveralls": "^3.1.1",
69
- "esbuild": "^0.27.3",
70
- "jsdom": "^28.1.0",
71
- "timekeeper": "^2.3.1",
72
- "typescript": "5.9.3",
73
- "vitest": "^4.0.18"
74
- }
75
- }
1
+ {
2
+ "name": "samlesa",
3
+ "version": "3.4.3",
4
+ "description": "High-level API for Single Sign On (SAML 2.0) baseed on samlify ",
5
+ "main": "build/index.js",
6
+ "keywords": [
7
+ "nodejs",
8
+ "saml2",
9
+ "sso",
10
+ "slo",
11
+ "metadata"
12
+ ],
13
+ "type": "module",
14
+ "typings": "types/index.d.ts",
15
+ "scripts": {
16
+ "build": "tsc && copyfiles -u 1 src/schema/**/* build/src",
17
+ "docs": "docsify serve -o docs",
18
+ "lint": "tslint -p .",
19
+ "lint:fix": "tslint -p . --fix",
20
+ "test": "vitest",
21
+ "test:watch": "vitest --watch",
22
+ "test:coverage": "vitest run --coverage",
23
+ "hooks:postinstall": "mklink /J .git\\hooks\\pre-commit .pre-commit.sh || copy .pre-commit.sh .git\\hooks\\pre-commit"
24
+ },
25
+ "exports": {
26
+ ".": {
27
+ "types": "./types/index.d.ts",
28
+ "import": "./build/index.js"
29
+ }
30
+ },
31
+ "files": [
32
+ "build",
33
+ "types"
34
+ ],
35
+ "contributors": [
36
+ "Veclea <vemocle@gmail.com>"
37
+ ],
38
+ "author": "Veclea",
39
+ "repository": {
40
+ "url": "https://github.com/Veclea/samlify.git",
41
+ "type": "git"
42
+ },
43
+ "license": "MIT",
44
+ "dependencies": {
45
+ "@xmldom/xmldom": "^0.9.8",
46
+ "axios": "^1.13.6",
47
+ "camelcase": "^9.0.0",
48
+ "cross-env": "^10.1.0",
49
+ "iconv-lite": "^0.7.2",
50
+ "ts-node": "^10.9.2",
51
+ "vite-tsconfig-paths": "^6.1.1",
52
+ "xml": "^1.0.1",
53
+ "xml-crypto": "^6.1.2",
54
+ "xml-crypto-next": "^7.0.4",
55
+ "xml-encryption-next": "^4.6.0",
56
+ "xml-escape": "^1.1.0",
57
+ "xml2js": "^0.6.2",
58
+ "xmllint-wasm": "^5.2.0",
59
+ "xpath": "^0.0.34"
60
+ },
61
+ "devDependencies": {
62
+ "@types/node": "^25.5.0",
63
+ "@types/pako": "2.0.4",
64
+ "@types/uuid": "11.0.0",
65
+ "@vitest/coverage-istanbul": "^4.1.2",
66
+ "@vitest/coverage-v8": "4.1.2",
67
+ "copyfiles": "^2.4.1",
68
+ "coveralls": "^3.1.1",
69
+ "esbuild": "^0.27.4",
70
+ "jsdom": "^29.0.1",
71
+ "timekeeper": "^2.3.1",
72
+ "typescript": "6.0.2",
73
+ "vitest": "^4.1.2"
74
+ }
75
+ }
@@ -1,5 +1,6 @@
1
1
  import type { BindingContext } from './entity.js';
2
2
  import { IdentityProviderConstructor as IdentityProvider, ServiceProviderConstructor as ServiceProvider } from "./types.js";
3
+ import { Base64LoginResponseParams } from "./types.js";
3
4
  /**
4
5
  * @desc Generate a base64 encoded login request
5
6
  * @param {string} referenceTagXPath reference uri
@@ -7,16 +8,7 @@ import { IdentityProviderConstructor as IdentityProvider, ServiceProviderConstru
7
8
  * @param customTagReplacement
8
9
  */
9
10
  declare function soapLoginRequest(referenceTagXPath: string, entity: any, customTagReplacement?: (template: string) => BindingContext): any;
10
- /**
11
- * @desc Generate a base64 encoded login response
12
- * @param {object} requestInfo corresponding request, used to obtain the id
13
- * @param {object} entity object includes both idp and sp
14
- * @param {object} user current logged user (e.g. req.user)
15
- * @param {function} customTagReplacement used when developers have their own login response template
16
- * @param {boolean} encryptThenSign whether or not to encrypt then sign first (if signing). Defaults to sign-then-encrypt
17
- * @param AttributeStatement
18
- */
19
- declare function soapLoginResponse(requestInfo: any | undefined, entity: any, user?: any, customTagReplacement?: (template: string) => BindingContext, encryptThenSign?: boolean, AttributeStatement?: never[]): Promise<BindingContext>;
11
+ declare function soapLoginResponse(params: Base64LoginResponseParams): Promise<BindingContext>;
20
12
  declare function parseLoginRequestResolve(params: {
21
13
  idp: IdentityProvider;
22
14
  sp: ServiceProvider;
@@ -1 +1 @@
1
- {"version":3,"file":"binding-artifact.d.ts","sourceRoot":"","sources":["../../src/binding-artifact.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,aAAa,CAAC;AAMhD,OAAO,EACH,2BAA2B,IAAI,gBAAgB,EAC/C,0BAA0B,IAAI,eAAe,EAChD,MAAM,YAAY,CAAC;AAwCpB;;;;;GAKG;AACH,iBAAS,gBAAgB,CAAC,iBAAiB,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,oBAAoB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,cAAc,GAAG,GAAG,CAkGlI;AAED;;;;;;;;GAQG;AACH,iBAAe,iBAAiB,CAAC,WAAW,EAAE,GAAG,YAAK,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,GAAE,GAAQ,EAAE,oBAAoB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,cAAc,EAAE,eAAe,GAAE,OAAe,EAAE,kBAAkB,UAAK,GAAG,OAAO,CAAC,cAAc,CAAC,CAuIpO;AAED,iBAAe,wBAAwB,CAAC,MAAM,EAAE;IAC5C,GAAG,EAAE,gBAAgB,CAAC;IACtB,EAAE,EAAE,eAAe,CAAC;IACpB,GAAG,EAAE,MAAM,CAAA;CACd;;;GAqDA;AAED,iBAAe,yBAAyB,CAAC,MAAM,EAAE;IAAE,GAAG,EAAE,gBAAgB,CAAC;IAAC,EAAE,EAAE,eAAe,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE;;;GAgP3G;AAED,QAAA,MAAM,mBAAmB;;;;;CAOxB,CAAC;AAEF,eAAe,mBAAmB,CAAC"}
1
+ {"version":3,"file":"binding-artifact.d.ts","sourceRoot":"","sources":["../../src/binding-artifact.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,aAAa,CAAC;AAShD,OAAO,EACH,2BAA2B,IAAI,gBAAgB,EAC/C,0BAA0B,IAAI,eAAe,EAChD,MAAM,YAAY,CAAC;AAiBpB,OAAO,EAAC,yBAAyB,EAAC,MAAM,YAAY,CAAC;AAwBrD;;;;;GAKG;AACH,iBAAS,gBAAgB,CAAC,iBAAiB,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,oBAAoB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,cAAc,GAAG,GAAG,CAkGlI;AAqCD,iBAAe,iBAAiB,CAAC,MAAM,EAAE,yBAAyB,GAAG,OAAO,CAAC,cAAc,CAAC,CA0F3F;AAID,iBAAe,wBAAwB,CAAC,MAAM,EAAE;IAC5C,GAAG,EAAE,gBAAgB,CAAC;IACtB,EAAE,EAAE,eAAe,CAAC;IACpB,GAAG,EAAE,MAAM,CAAA;CACd;;;GAqDA;AAED,iBAAe,yBAAyB,CAAC,MAAM,EAAE;IAAE,GAAG,EAAE,gBAAgB,CAAC;IAAC,EAAE,EAAE,eAAe,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE;;;GAgP3G;AAED,QAAA,MAAM,mBAAmB;;;;;CAOxB,CAAC;AAEF,eAAe,mBAAmB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"entity-idp.d.ts","sourceRoot":"","sources":["../../src/entity-idp.ts"],"names":[],"mappings":"AAYA,OAAO,MAAM,EAAE,EAAC,KAAK,gBAAgB,EAAC,MAAM,aAAa,CAAC;AAC1D,OAAO,EACH,0BAA0B,IAAI,eAAe,EAE7C,wBAAwB,EACxB,KAAK,wBAAwB,EAChC,MAAM,YAAY,CAAC;AAMpB,OAAO,EAAO,KAAK,UAAU,EAAC,MAAM,WAAW,CAAC;AAEhD,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,aAAa,CAAC;AAEhD;;GAEG;AACH,MAAM,CAAC,OAAO,WAAW,KAAK,EAAE,wBAAwB,oBAEvD;AAED;;GAEG;AACH,qBAAa,gBAAiB,SAAQ,MAAM;IAEhC,UAAU,EAAE,wBAAwB,CAAC;gBAEjC,UAAU,EAAE,wBAAwB;IAWhD;;;OAGG;IACU,mBAAmB,CAAC,MAAM,EAAE;QACrC,EAAE,EAAE,eAAe,CAAC;QACpB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAClC,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAC3B,oBAAoB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,cAAc,CAAC;QAC5D,eAAe,CAAC,EAAE,OAAO,CAAC;QAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,kBAAkB,CAAC,EAAE,EAAE,CAAC;QACxB,OAAO,CAAC,EAAE,KAAK,CAAC;KACnB;IAuDD;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,gBAAgB;CAYhF"}
1
+ {"version":3,"file":"entity-idp.d.ts","sourceRoot":"","sources":["../../src/entity-idp.ts"],"names":[],"mappings":"AAYA,OAAO,MAAM,EAAE,EAAC,KAAK,gBAAgB,EAAC,MAAM,aAAa,CAAC;AAC1D,OAAO,EACH,0BAA0B,IAAI,eAAe,EAE7C,wBAAwB,EACxB,KAAK,wBAAwB,EAChC,MAAM,YAAY,CAAC;AAOpB,OAAO,EAAO,KAAK,UAAU,EAAC,MAAM,WAAW,CAAC;AAEhD,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,aAAa,CAAC;AAEhD;;GAEG;AACH,MAAM,CAAC,OAAO,WAAW,KAAK,EAAE,wBAAwB,oBAEvD;AAED;;GAEG;AACH,qBAAa,gBAAiB,SAAQ,MAAM;IAEhC,UAAU,EAAE,wBAAwB,CAAC;gBAEjC,UAAU,EAAE,wBAAwB;IAWhD;;;OAGG;IACU,mBAAmB,CAAC,MAAM,EAAE;QACrC,EAAE,EAAE,eAAe,CAAC;QACpB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAClC,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAC3B,oBAAoB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,cAAc,CAAC;QAC5D,eAAe,CAAC,EAAE,OAAO,CAAC;QAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,kBAAkB,CAAC,EAAE,EAAE,CAAC;QACxB,OAAO,CAAC,EAAE,KAAK,CAAC;KAEnB;IAiED;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,gBAAgB;CAYhF"}
@@ -32,6 +32,7 @@ export declare function extractSpData(context: string): any;
32
32
  export declare function extractIdp(context: string): any;
33
33
  export declare function extractSp(context: string): any;
34
34
  export declare function extractAuthRequest(context: string): any;
35
- export declare function extractResponse(context: string, ass: any): any;
35
+ export declare function extractResponse(context: string): any;
36
+ export declare function extractArtifactResolve(context: string): any;
36
37
  export {};
37
38
  //# sourceMappingURL=extractor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"extractor.d.ts","sourceRoot":"","sources":["../../src/extractor.ts"],"names":[],"mappings":"AAMA,UAAU,cAAc;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,EAAE,CAAC;IAClC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,GAAG,CAAC;IAEnC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAOD,MAAM,MAAM,eAAe,GAAG,cAAc,EAAE,CAAC;AA4B/C,eAAO,MAAM,kBAAkB,EAAE,eAsFhC,CAAC;AAKF,eAAO,MAAM,qBAAqB,EAAE,eAKnC,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,eAKpC,CAAC;AAEF,eAAO,MAAM,yBAAyB,EAAE,eAGvC,CAAC;AAEF,eAAO,MAAM,iCAAiC,EAAE,eAG/C,CAAC;AAEF,eAAO,MAAM,0BAA0B,EAAE,eAGxC,CAAC;AAEF,eAAO,MAAM,mBAAmB,EAAE,CAAC,CAAC,SAAS,EAAE,GAAG,KAAK,eAAe,CAWrE,CAAC;AAqMF,eAAO,MAAM,mBAAmB,EAAE,eAMjC,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,eAIlC,CAAC;AAKF,eAAO,MAAM,iBAAiB,EAAE,eAiI/B,CAAC;AAOF,eAAO,MAAM,gBAAgB,EAAE,eAyL9B,CAAC;AAEF,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,OAiN/D;AASD,eAAO,MAAM,2BAA2B,EAAE,eAkZzC,CAAC;AAIF;;GAEG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,OAkRrE;AAKD,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,OAE5C;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,OAEzC;AAGD,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,OAExC;AACD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,OAEjD;AACD,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAC,GAAG,EAAC,GAAG,OAEtD"}
1
+ {"version":3,"file":"extractor.d.ts","sourceRoot":"","sources":["../../src/extractor.ts"],"names":[],"mappings":"AAMA,UAAU,cAAc;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,EAAE,CAAC;IAClC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,GAAG,CAAC;IAEnC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAOD,MAAM,MAAM,eAAe,GAAG,cAAc,EAAE,CAAC;AA4B/C,eAAO,MAAM,kBAAkB,EAAE,eAsFhC,CAAC;AAKF,eAAO,MAAM,qBAAqB,EAAE,eAsBnC,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,eAKpC,CAAC;AAEF,eAAO,MAAM,yBAAyB,EAAE,eAGvC,CAAC;AAEF,eAAO,MAAM,iCAAiC,EAAE,eAG/C,CAAC;AAEF,eAAO,MAAM,0BAA0B,EAAE,eAGxC,CAAC;AAEF,eAAO,MAAM,mBAAmB,EAAE,CAAC,CAAC,SAAS,EAAE,GAAG,KAAK,eAAe,CAWrE,CAAC;AAqMF,eAAO,MAAM,mBAAmB,EAAE,eAMjC,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,eAIlC,CAAC;AAKF,eAAO,MAAM,iBAAiB,EAAE,eAiI/B,CAAC;AAOF,eAAO,MAAM,gBAAgB,EAAE,eAyL9B,CAAC;AAEF,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,OAiN/D;AASD,eAAO,MAAM,2BAA2B,EAAE,eAkZzC,CAAC;AAIF;;GAEG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,OAkRrE;AAKD,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,OAE5C;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,OAEzC;AAGD,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,OAExC;AACD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,OAEjD;AACD,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,OAE9C;AACD,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,OAErD"}
@@ -1,3 +1,20 @@
1
+ /**
2
+ * 验证 SAML 消息(普通或 SOAP)
3
+ * @param xml XML 字符串
4
+ * @param isSoap 是否为 SOAP 消息,默认 false
5
+ * @returns true 表示验证通过,否则抛出错误
6
+ * @throws 当检测到 XXE 或验证失败时抛出错误
7
+ */
1
8
  export declare const validate: (xml: string, isSoap?: boolean) => Promise<boolean>;
2
- export declare const validateMetadata: (xml: string, isParse?: boolean) => Promise<any>;
9
+ /**
10
+ * 验证 SAML 元数据,并可选择解析元数据类型
11
+ * @param xml XML 字符串
12
+ * @param isParse 是否解析并返回元数据类型,默认 false
13
+ * @returns 验证通过时:若 isParse 为 true 返回 { isValid: true, metadataType: string },否则返回 true;
14
+ * 验证失败时返回 Error 对象(保持原行为)
15
+ */
16
+ export declare const validateMetadata: (xml: string, isParse?: boolean) => Promise<true | Error | readonly import("xmllint-wasm").XMLValidationError[] | {
17
+ isValid: boolean;
18
+ metadataType: string;
19
+ }>;
3
20
  //# sourceMappingURL=schemaValidator.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"schemaValidator.d.ts","sourceRoot":"","sources":["../../src/schemaValidator.ts"],"names":[],"mappings":"AA8EA,eAAO,MAAM,QAAQ,GAAU,KAAK,MAAM,EAAC,SAAQ,OAAe,qBAmCjE,CAAC;AACF,eAAO,MAAM,gBAAgB,GAAU,KAAK,MAAM,EAAC,UAAS,OAAe,iBA6D1E,CAAC"}
1
+ {"version":3,"file":"schemaValidator.d.ts","sourceRoot":"","sources":["../../src/schemaValidator.ts"],"names":[],"mappings":"AAsFA;;;;;;GAMG;AACH,eAAO,MAAM,QAAQ,GAAU,KAAK,MAAM,EAAE,SAAQ,OAAe,qBA4BlE,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,GAAU,KAAK,MAAM,EAAE,UAAS,OAAe;;;EA+D3E,CAAC"}
@@ -9,6 +9,8 @@ export declare enum BindingNamespace {
9
9
  SimpleSign = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign",
10
10
  Artifact = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact"
11
11
  }
12
+ export declare const NamespaceBindingMap: Record<string, 'redirect' | 'post' | 'simplesign' | 'artifact'>;
13
+ declare function getBindingName(uri: string): 'redirect' | 'post' | 'simplesign' | 'artifact' | undefined;
12
14
  export declare enum MessageSignatureOrder {
13
15
  STE = "sign-then-encrypt",
14
16
  ETS = "encrypt-then-sign"
@@ -46,6 +48,12 @@ declare const namespace: {
46
48
  artifact: string;
47
49
  soap: string;
48
50
  };
51
+ bindMap: {
52
+ 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect': string;
53
+ 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST': string;
54
+ 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign': string;
55
+ 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact': string;
56
+ };
49
57
  names: {
50
58
  protocol: string;
51
59
  assertion: string;
@@ -260,5 +268,5 @@ declare const elementsOrder: {
260
268
  onelogin: string[];
261
269
  shibboleth: string[];
262
270
  };
263
- export { namespace, tags, algorithms, wording, elementsOrder, messageConfigurations };
271
+ export { namespace, tags, algorithms, wording, elementsOrder, messageConfigurations, getBindingName };
264
272
  //# sourceMappingURL=urn.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"urn.d.ts","sourceRoot":"","sources":["../../src/urn.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,oBAAY,gBAAgB;IAC1B,QAAQ,uDAAuD;IAC/D,IAAI,mDAAmD;IACvD,UAAU,8DAA8D;IACxE,QAAQ,uDAAuD;CAChE;AAED,oBAAY,qBAAqB;IAC/B,GAAG,sBAAsB;IACzB,GAAG,sBAAsB;CAC1B;AAED,oBAAY,UAAU;IAEpB,OAAO,+CAA+C;IACtD,SAAS,iDAAiD;IAC1D,SAAS,iDAAiD;IAC1D,eAAe,uDAAuD;IAEtE,UAAU,mDAAmD;IAC7D,sBAAsB,8DAA8D;IACpF,mBAAmB,2DAA2D;IAC9E,cAAc,sDAAsD;IACpE,cAAc,sDAAsD;IACpE,SAAS,iDAAiD;IAC1D,cAAc,sDAAsD;IACpE,aAAa,qDAAqD;IAClE,kBAAkB,0DAA0D;IAC5E,aAAa,qDAAqD;IAClE,kBAAkB,0DAA0D;IAC5E,wBAAwB,gEAAgE;IACxF,qBAAqB,6DAA6D;IAClF,oBAAoB,4DAA4D;IAChF,qBAAqB,6DAA6D;IAClF,gBAAgB,wDAAwD;IACxE,kBAAkB,0DAA0D;IAC5E,gBAAgB,wDAAwD;IACxE,kBAAkB,0DAA0D;CAC7E;AAED,QAAA,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8Fd,CAAC;AAEF,QAAA,MAAM,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+BT,CAAC;AAEF,QAAA,MAAM,qBAAqB;;;;;CAK1B,CAAC;AAEF,QAAA,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAmFZ;;;WAGG;;;;;;;;;;;;;CAwBN,CAAC;AAaF,oBAAY,UAAU;IACpB,WAAW,gBAAgB;IAC3B,YAAY,iBAAiB;IAC7B,aAAa,kBAAkB;IAC/B,cAAc,mBAAmB;CAClC;AAED,QAAA,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;CAyBZ,CAAC;AAIF,QAAA,MAAM,aAAa;;;;CAIlB,CAAC;AAEF,OAAO,EAAC,SAAS,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAC,CAAC"}
1
+ {"version":3,"file":"urn.d.ts","sourceRoot":"","sources":["../../src/urn.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,oBAAY,gBAAgB;IAC1B,QAAQ,uDAAuD;IAC/D,IAAI,mDAAmD;IACvD,UAAU,8DAA8D;IACxE,QAAQ,uDAAuD;CAChE;AACD,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,GAAG,YAAY,GAAG,UAAU,CAK/F,CAAC;AAGD,iBAAS,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,GAAG,YAAY,GAAG,UAAU,GAAG,SAAS,CAEjG;AAED,oBAAY,qBAAqB;IAC/B,GAAG,sBAAsB;IACzB,GAAG,sBAAsB;CAC1B;AAED,oBAAY,UAAU;IAEpB,OAAO,+CAA+C;IACtD,SAAS,iDAAiD;IAC1D,SAAS,iDAAiD;IAC1D,eAAe,uDAAuD;IAEtE,UAAU,mDAAmD;IAC7D,sBAAsB,8DAA8D;IACpF,mBAAmB,2DAA2D;IAC9E,cAAc,sDAAsD;IACpE,cAAc,sDAAsD;IACpE,SAAS,iDAAiD;IAC1D,cAAc,sDAAsD;IACpE,aAAa,qDAAqD;IAClE,kBAAkB,0DAA0D;IAC5E,aAAa,qDAAqD;IAClE,kBAAkB,0DAA0D;IAC5E,wBAAwB,gEAAgE;IACxF,qBAAqB,6DAA6D;IAClF,oBAAoB,4DAA4D;IAChF,qBAAqB,6DAA6D;IAClF,gBAAgB,wDAAwD;IACxE,kBAAkB,0DAA0D;IAC5E,gBAAgB,wDAAwD;IACxE,kBAAkB,0DAA0D;CAC7E;AAED,QAAA,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoGd,CAAC;AAEF,QAAA,MAAM,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+BT,CAAC;AAEF,QAAA,MAAM,qBAAqB;;;;;CAK1B,CAAC;AAEF,QAAA,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAmFZ;;;WAGG;;;;;;;;;;;;;CAwBN,CAAC;AAaF,oBAAY,UAAU;IACpB,WAAW,gBAAgB;IAC3B,YAAY,iBAAiB;IAC7B,aAAa,kBAAkB;IAC/B,cAAc,mBAAmB;CAClC;AAED,QAAA,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;CAyBZ,CAAC;AAIF,QAAA,MAAM,aAAa;;;;CAIlB,CAAC;AAEF,OAAO,EAAC,SAAS,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAC,cAAc,EAAC,CAAC"}