@slates/provider 1.0.0-rc.2 → 1.0.0-rc.20
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.
- package/dist/index.cjs +2847 -2
- package/dist/index.d.cts +1019 -0
- package/dist/index.d.ts +1019 -9
- package/dist/index.module.js +2737 -2
- package/package.json +9 -6
- package/src/action/action.ts +92 -4
- package/src/action/attachment.ts +87 -0
- package/src/action/builder.test.ts +136 -0
- package/src/action/builder.ts +113 -15
- package/src/action/directUpload.ts +59 -0
- package/src/action/getFileUrlTool.ts +41 -0
- package/src/action/index.ts +4 -2
- package/src/action/publicTool.ts +36 -0
- package/src/action/scopes.test.ts +76 -0
- package/src/action/scopes.ts +103 -0
- package/src/action/tool.ts +22 -6
- package/src/action/trigger.ts +19 -11
- package/src/adapter/adapter.ts +38 -0
- package/src/adapter/index.test.ts +322 -0
- package/src/adapter/index.ts +2 -0
- package/src/adapter/spec.ts +154 -0
- package/src/auth/auth.ts +3 -3
- package/src/auth/index.ts +2 -0
- package/src/auth/oauth.test.ts +83 -0
- package/src/auth/oauth.ts +150 -0
- package/src/auth/redact.test.ts +103 -0
- package/src/auth/redact.ts +111 -0
- package/src/auth/types.ts +72 -13
- package/src/axios/http.test.ts +83 -0
- package/src/axios/index.ts +116 -10
- package/src/axios/trace.test.ts +377 -0
- package/src/axios/trace.ts +803 -0
- package/src/config/config.ts +14 -3
- package/src/context/context.ts +264 -17
- package/src/context/hook.test.ts +46 -0
- package/src/context/hook.ts +15 -6
- package/src/context/index.ts +1 -0
- package/src/context/pQueue.ts +4 -0
- package/src/crypto/hmac.test.ts +40 -0
- package/src/crypto/hmac.ts +32 -0
- package/src/crypto/index.ts +1 -0
- package/src/docs.ts +22 -0
- package/src/error/api.test.ts +114 -0
- package/src/error/api.ts +236 -0
- package/src/error/axios.ts +220 -0
- package/src/error/base.test.ts +178 -0
- package/src/error/base.ts +108 -2
- package/src/error/declaration.ts +7 -2
- package/src/error/defaults.ts +50 -0
- package/src/error/index.ts +4 -0
- package/src/error/service.ts +170 -0
- package/src/error/types.ts +75 -0
- package/src/error/utils.ts +157 -0
- package/src/file/index.test.ts +36 -0
- package/src/file/index.ts +27 -0
- package/src/index.ts +8 -0
- package/src/logger/logger.test.ts +57 -0
- package/src/logger/logger.ts +72 -11
- package/src/module/resolveDefaultExport.test.ts +32 -0
- package/src/module/resolveDefaultExport.ts +14 -0
- package/src/object/index.test.ts +38 -0
- package/src/object/index.ts +12 -0
- package/src/specification/slate.ts +40 -4
- package/src/specification/specification.ts +10 -6
- package/src/webhook/graph.test.ts +37 -0
- package/src/webhook/graph.ts +22 -0
- package/src/webhook/index.ts +2 -0
- package/src/webhook/meta.test.ts +52 -0
- package/src/webhook/meta.ts +43 -0
- package/dist/action/action.d.ts +0 -98
- package/dist/action/action.d.ts.map +0 -1
- package/dist/action/builder.d.ts +0 -27
- package/dist/action/builder.d.ts.map +0 -1
- package/dist/action/index.d.ts +0 -5
- package/dist/action/index.d.ts.map +0 -1
- package/dist/action/tool.d.ts +0 -14
- package/dist/action/tool.d.ts.map +0 -1
- package/dist/action/trigger.d.ts +0 -32
- package/dist/action/trigger.d.ts.map +0 -1
- package/dist/auth/auth.d.ts +0 -30
- package/dist/auth/auth.d.ts.map +0 -1
- package/dist/auth/index.d.ts +0 -3
- package/dist/auth/index.d.ts.map +0 -1
- package/dist/auth/types.d.ts +0 -148
- package/dist/auth/types.d.ts.map +0 -1
- package/dist/axios/index.d.ts +0 -4
- package/dist/axios/index.d.ts.map +0 -1
- package/dist/config/config.d.ts +0 -26
- package/dist/config/config.d.ts.map +0 -1
- package/dist/config/index.d.ts +0 -2
- package/dist/config/index.d.ts.map +0 -1
- package/dist/context/context.d.ts +0 -20
- package/dist/context/context.d.ts.map +0 -1
- package/dist/context/hook.d.ts +0 -4
- package/dist/context/hook.d.ts.map +0 -1
- package/dist/context/index.d.ts +0 -2
- package/dist/context/index.d.ts.map +0 -1
- package/dist/error/base.d.ts +0 -5
- package/dist/error/base.d.ts.map +0 -1
- package/dist/error/declaration.d.ts +0 -6
- package/dist/error/declaration.d.ts.map +0 -1
- package/dist/error/index.d.ts +0 -3
- package/dist/error/index.d.ts.map +0 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.modern.js +0 -2
- package/dist/index.modern.js.map +0 -1
- package/dist/index.module.js.map +0 -1
- package/dist/index.umd.js +0 -2
- package/dist/index.umd.js.map +0 -1
- package/dist/logger/index.d.ts +0 -2
- package/dist/logger/index.d.ts.map +0 -1
- package/dist/logger/logger.d.ts +0 -20
- package/dist/logger/logger.d.ts.map +0 -1
- package/dist/specification/index.d.ts +0 -3
- package/dist/specification/index.d.ts.map +0 -1
- package/dist/specification/slate.d.ts +0 -15
- package/dist/specification/slate.d.ts.map +0 -1
- package/dist/specification/specification.d.ts +0 -30
- package/dist/specification/specification.d.ts.map +0 -1
- package/dist/specification/zero.d.ts +0 -13
- package/dist/specification/zero.d.ts.map +0 -1
- package/dist/tokens.d.ts +0 -34
- package/dist/tokens.d.ts.map +0 -1
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { createApiServiceError } from '../error/api';
|
|
2
|
+
|
|
3
|
+
export type OAuthTokenResponse = {
|
|
4
|
+
access_token?: unknown;
|
|
5
|
+
refresh_token?: unknown;
|
|
6
|
+
expires_in?: unknown;
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type OAuthRefreshTokenFallbackMode = 'nullish' | 'falsy';
|
|
11
|
+
|
|
12
|
+
export type OAuthExpiresAtOptions = {
|
|
13
|
+
nowMs?: number;
|
|
14
|
+
providerLabel?: string;
|
|
15
|
+
operation?: string;
|
|
16
|
+
required?: boolean;
|
|
17
|
+
expiresInType?: 'number' | 'numberLike';
|
|
18
|
+
message?: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type NormalizeOAuthTokenResponseOptions = OAuthExpiresAtOptions & {
|
|
22
|
+
previousRefreshToken?: string;
|
|
23
|
+
refreshTokenFallbackMode?: OAuthRefreshTokenFallbackMode;
|
|
24
|
+
accessTokenMessage?: string;
|
|
25
|
+
expiresInMessage?: string;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
let isOAuthTokenResponse = (value: unknown): value is OAuthTokenResponse =>
|
|
29
|
+
typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
30
|
+
|
|
31
|
+
let formatOAuthTokenResponseMessage = (
|
|
32
|
+
fieldLabel: string,
|
|
33
|
+
providerLabel = 'OAuth provider',
|
|
34
|
+
operation = 'token response'
|
|
35
|
+
) => `${providerLabel} OAuth ${operation} did not return ${fieldLabel}.`;
|
|
36
|
+
|
|
37
|
+
export let getOAuthExpiresAtFromExpiresIn = (
|
|
38
|
+
expiresIn: unknown,
|
|
39
|
+
options: OAuthExpiresAtOptions = {}
|
|
40
|
+
) => {
|
|
41
|
+
let {
|
|
42
|
+
nowMs = Date.now(),
|
|
43
|
+
providerLabel,
|
|
44
|
+
operation,
|
|
45
|
+
required = false,
|
|
46
|
+
expiresInType = 'numberLike',
|
|
47
|
+
message
|
|
48
|
+
} = options;
|
|
49
|
+
|
|
50
|
+
if (expiresIn === undefined || expiresIn === null || expiresIn === '') {
|
|
51
|
+
if (required) {
|
|
52
|
+
throw createApiServiceError(
|
|
53
|
+
message ??
|
|
54
|
+
formatOAuthTokenResponseMessage(
|
|
55
|
+
'a valid expires_in value',
|
|
56
|
+
providerLabel,
|
|
57
|
+
operation
|
|
58
|
+
),
|
|
59
|
+
{ reason: 'oauth_token_response' }
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let seconds =
|
|
67
|
+
typeof expiresIn === 'number'
|
|
68
|
+
? expiresIn
|
|
69
|
+
: expiresInType === 'numberLike' && typeof expiresIn === 'string'
|
|
70
|
+
? Number(expiresIn)
|
|
71
|
+
: Number.NaN;
|
|
72
|
+
|
|
73
|
+
if (!Number.isFinite(seconds)) {
|
|
74
|
+
throw createApiServiceError(
|
|
75
|
+
message ??
|
|
76
|
+
formatOAuthTokenResponseMessage('a valid expires_in value', providerLabel, operation),
|
|
77
|
+
{ reason: 'oauth_token_response' }
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return new Date(nowMs + seconds * 1000).toISOString();
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
let getRefreshToken = (
|
|
85
|
+
refreshToken: unknown,
|
|
86
|
+
previousRefreshToken: string | undefined,
|
|
87
|
+
fallbackMode: OAuthRefreshTokenFallbackMode
|
|
88
|
+
) => {
|
|
89
|
+
if (typeof refreshToken === 'string') {
|
|
90
|
+
if (fallbackMode === 'falsy' && !refreshToken) {
|
|
91
|
+
return previousRefreshToken;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return refreshToken;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (fallbackMode === 'falsy' && !refreshToken) {
|
|
98
|
+
return previousRefreshToken;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return refreshToken === undefined || refreshToken === null
|
|
102
|
+
? previousRefreshToken
|
|
103
|
+
: undefined;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export let normalizeOAuthTokenResponse = (
|
|
107
|
+
data: unknown,
|
|
108
|
+
options: NormalizeOAuthTokenResponseOptions = {}
|
|
109
|
+
) => {
|
|
110
|
+
let {
|
|
111
|
+
providerLabel,
|
|
112
|
+
operation,
|
|
113
|
+
previousRefreshToken,
|
|
114
|
+
refreshTokenFallbackMode = 'nullish',
|
|
115
|
+
accessTokenMessage,
|
|
116
|
+
expiresInMessage,
|
|
117
|
+
...expiresAtOptions
|
|
118
|
+
} = options;
|
|
119
|
+
|
|
120
|
+
if (!isOAuthTokenResponse(data)) {
|
|
121
|
+
throw createApiServiceError(
|
|
122
|
+
accessTokenMessage ??
|
|
123
|
+
formatOAuthTokenResponseMessage('a token response object', providerLabel, operation),
|
|
124
|
+
{ reason: 'oauth_token_response' }
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (typeof data.access_token !== 'string' || !data.access_token) {
|
|
129
|
+
throw createApiServiceError(
|
|
130
|
+
accessTokenMessage ??
|
|
131
|
+
formatOAuthTokenResponseMessage('an access token', providerLabel, operation),
|
|
132
|
+
{ reason: 'oauth_token_response' }
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
token: data.access_token,
|
|
138
|
+
refreshToken: getRefreshToken(
|
|
139
|
+
data.refresh_token,
|
|
140
|
+
previousRefreshToken,
|
|
141
|
+
refreshTokenFallbackMode
|
|
142
|
+
),
|
|
143
|
+
expiresAt: getOAuthExpiresAtFromExpiresIn(data.expires_in, {
|
|
144
|
+
...expiresAtOptions,
|
|
145
|
+
providerLabel,
|
|
146
|
+
operation,
|
|
147
|
+
message: expiresInMessage
|
|
148
|
+
})
|
|
149
|
+
};
|
|
150
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import type { SlateAttachment } from '../action/attachment';
|
|
3
|
+
import { AuthConfigSecretRedactor, redactUrlAttachmentSecrets } from './redact';
|
|
4
|
+
|
|
5
|
+
describe('AuthConfigSecretRedactor', () => {
|
|
6
|
+
let authConfig = { token: '123SECRET', nested: { token2: 'also_secret' } };
|
|
7
|
+
|
|
8
|
+
it('redacts values that match a secret to placeholders', () => {
|
|
9
|
+
let redactor = new AuthConfigSecretRedactor(authConfig);
|
|
10
|
+
|
|
11
|
+
expect(
|
|
12
|
+
redactor.redact({
|
|
13
|
+
headers: { Authorization: 'Bearer 123SECRET' },
|
|
14
|
+
query: { key: 'also_secret' }
|
|
15
|
+
})
|
|
16
|
+
).toEqual({
|
|
17
|
+
headers: { Authorization: 'Bearer 123SECRET' }, // whole-value match only, not substring
|
|
18
|
+
query: { key: '$$MT$secret$authConfig$nested.token2' }
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('redacts a bare string value', () => {
|
|
23
|
+
let redactor = new AuthConfigSecretRedactor(authConfig);
|
|
24
|
+
expect(redactor.redact('123SECRET')).toBe('$$MT$secret$authConfig$token');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('leaves non-secret strings untouched', () => {
|
|
28
|
+
let redactor = new AuthConfigSecretRedactor(authConfig);
|
|
29
|
+
expect(redactor.redact('https://example.com/file.pdf')).toBe(
|
|
30
|
+
'https://example.com/file.pdf'
|
|
31
|
+
);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('recurses through arrays and nested objects', () => {
|
|
35
|
+
let redactor = new AuthConfigSecretRedactor(authConfig);
|
|
36
|
+
expect(redactor.redact({ list: ['also_secret', 'plain'] })).toEqual({
|
|
37
|
+
list: ['$$MT$secret$authConfig$nested.token2', 'plain']
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('never registers an empty-string secret', () => {
|
|
42
|
+
let redactor = new AuthConfigSecretRedactor({ token: '' });
|
|
43
|
+
expect(redactor.redact({ a: '', b: '' })).toEqual({ a: '', b: '' });
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('is a no-op when there is no auth config', () => {
|
|
47
|
+
let redactor = new AuthConfigSecretRedactor(undefined);
|
|
48
|
+
expect(redactor.redact({ a: 'b' })).toEqual({ a: 'b' });
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('redactUrlAttachmentSecrets', () => {
|
|
53
|
+
let authConfig = { token: '123SECRET', nested: { token2: 'also_secret' } };
|
|
54
|
+
|
|
55
|
+
let urlAttachment = (
|
|
56
|
+
overrides: Partial<Extract<SlateAttachment['content'], { type: 'url' }>> = {}
|
|
57
|
+
): SlateAttachment => ({
|
|
58
|
+
mimeType: 'application/pdf',
|
|
59
|
+
content: { type: 'url', url: 'https://example.com/file.pdf', ...overrides }
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('redacts headers and query on url attachments', () => {
|
|
63
|
+
let attachments: SlateAttachment[] = [
|
|
64
|
+
urlAttachment({
|
|
65
|
+
headers: { Authorization: 'Bearer 123SECRET' },
|
|
66
|
+
query: { key: 'also_secret' }
|
|
67
|
+
})
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
expect(redactUrlAttachmentSecrets(attachments, authConfig)).toEqual([
|
|
71
|
+
urlAttachment({
|
|
72
|
+
headers: { Authorization: 'Bearer 123SECRET' },
|
|
73
|
+
query: { key: '$$MT$secret$authConfig$nested.token2' }
|
|
74
|
+
})
|
|
75
|
+
]);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('leaves url attachments with no headers/query untouched', () => {
|
|
79
|
+
let attachments = [urlAttachment()];
|
|
80
|
+
expect(redactUrlAttachmentSecrets(attachments, authConfig)).toEqual(attachments);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('leaves content and upload_reference attachments untouched', () => {
|
|
84
|
+
let attachments: SlateAttachment[] = [
|
|
85
|
+
{
|
|
86
|
+
mimeType: 'text/plain',
|
|
87
|
+
content: { type: 'content', encoding: 'utf-8', content: 'hi' }
|
|
88
|
+
},
|
|
89
|
+
{ content: { type: 'upload_reference', referenceId: 'ref_1' } }
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
expect(redactUrlAttachmentSecrets(attachments, authConfig)).toEqual(attachments);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('is a no-op when there is no auth config', () => {
|
|
96
|
+
let attachments = [urlAttachment({ headers: { Authorization: 'Bearer 123SECRET' } })];
|
|
97
|
+
expect(redactUrlAttachmentSecrets(attachments, undefined)).toBe(attachments);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('passes an undefined attachment list through unchanged', () => {
|
|
101
|
+
expect(redactUrlAttachmentSecrets(undefined, authConfig)).toBeUndefined();
|
|
102
|
+
});
|
|
103
|
+
});
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { SlateAttachment } from '../action/attachment';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Placeholder prefix used to mark a redacted auth-config secret. Must match the hub's
|
|
5
|
+
* AuthConfigSecretSerializer (oss/src/slates/apps/hub/src/lib/secretSerializer.ts) exactly --
|
|
6
|
+
* the hub resolves these placeholders back to live secret values when it proxies an attachment.
|
|
7
|
+
*/
|
|
8
|
+
export let AUTH_CONFIG_SECRET_PLACEHOLDER_PREFIX = '$$MT$secret$authConfig$';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Walks an auth config object and replaces any string value that exactly matches one of its
|
|
12
|
+
* leaf values with a `$$MT$secret$authConfig$<path>` placeholder. Literal, whole-value
|
|
13
|
+
* substitution only -- not a substring scan, and not a heuristic key-name/value-shape guess
|
|
14
|
+
* like the http trace redactor in '../axios/trace'.
|
|
15
|
+
*/
|
|
16
|
+
export class AuthConfigSecretRedactor {
|
|
17
|
+
private readonly secretToPlaceholder = new Map<string, string>();
|
|
18
|
+
|
|
19
|
+
public constructor(authConfig: Record<string, unknown> | undefined) {
|
|
20
|
+
if (authConfig) this.buildSecretMap(authConfig);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public redact<T>(value: T): T {
|
|
24
|
+
return this.transform(value, stringValue => {
|
|
25
|
+
return this.secretToPlaceholder.get(stringValue) ?? stringValue;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
private buildSecretMap(value: unknown, path: string[] = []): void {
|
|
30
|
+
if (typeof value === 'string') {
|
|
31
|
+
// Never register "" -- it would replace every empty header/query value with a placeholder.
|
|
32
|
+
if (value.length === 0) return;
|
|
33
|
+
|
|
34
|
+
this.secretToPlaceholder.set(
|
|
35
|
+
value,
|
|
36
|
+
`${AUTH_CONFIG_SECRET_PLACEHOLDER_PREFIX}${path.join('.')}`
|
|
37
|
+
);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (Array.isArray(value)) {
|
|
42
|
+
value.forEach((item, index) => {
|
|
43
|
+
this.buildSecretMap(item, [...path, String(index)]);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (this.isPlainObject(value)) {
|
|
50
|
+
Object.entries(value).forEach(([key, nestedValue]) => {
|
|
51
|
+
this.buildSecretMap(nestedValue, [...path, key]);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private transform<T>(value: T, transformString: (value: string) => string): T {
|
|
57
|
+
if (typeof value === 'string') {
|
|
58
|
+
return transformString(value) as T;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (Array.isArray(value)) {
|
|
62
|
+
return value.map(item => this.transform(item, transformString)) as T;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (this.isPlainObject(value)) {
|
|
66
|
+
let result: Record<string, unknown> = {};
|
|
67
|
+
|
|
68
|
+
Object.entries(value).forEach(([key, nestedValue]) => {
|
|
69
|
+
result[key] = this.transform(nestedValue, transformString);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
return result as T;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return value;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
79
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Applied to every finalized attachment list right before it's sent over
|
|
85
|
+
* `slates/action.tool.invoke` (see @slates/provider-handler). Only `type: 'url'` attachments
|
|
86
|
+
* carry headers/query -- content/upload_reference attachments pass through untouched.
|
|
87
|
+
*/
|
|
88
|
+
export let redactUrlAttachmentSecrets = (
|
|
89
|
+
attachments: SlateAttachment[] | undefined,
|
|
90
|
+
authConfig: Record<string, unknown> | undefined
|
|
91
|
+
): SlateAttachment[] | undefined => {
|
|
92
|
+
if (!authConfig || !attachments) return attachments;
|
|
93
|
+
|
|
94
|
+
let redactor = new AuthConfigSecretRedactor(authConfig);
|
|
95
|
+
|
|
96
|
+
return attachments.map(attachment => {
|
|
97
|
+
if (attachment.content.type !== 'url') return attachment;
|
|
98
|
+
if (!attachment.content.headers && !attachment.content.query) return attachment;
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
...attachment,
|
|
102
|
+
content: {
|
|
103
|
+
...attachment.content,
|
|
104
|
+
headers: attachment.content.headers
|
|
105
|
+
? redactor.redact(attachment.content.headers)
|
|
106
|
+
: undefined,
|
|
107
|
+
query: attachment.content.query ? redactor.redact(attachment.content.query) : undefined
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
});
|
|
111
|
+
};
|
package/src/auth/types.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import z from 'zod';
|
|
1
|
+
import type z from 'zod';
|
|
2
|
+
import type { SlateAuthDocsReference } from '../docs';
|
|
3
|
+
|
|
4
|
+
type SlateAuthCallbackConfig = Record<string, any>;
|
|
2
5
|
|
|
3
6
|
export type SlateAuthWithOauth<
|
|
4
7
|
InputType extends {},
|
|
@@ -19,12 +22,14 @@ export type SlateAuthWithOauth<
|
|
|
19
22
|
scope: string;
|
|
20
23
|
}[];
|
|
21
24
|
|
|
25
|
+
docs?: SlateAuthDocsReference[];
|
|
26
|
+
|
|
22
27
|
inputSchema?: z.ZodType<InputType>;
|
|
23
28
|
|
|
24
29
|
onInputChanged?: (params: {
|
|
25
30
|
previousInput: InputType | null;
|
|
26
31
|
newInput: InputType;
|
|
27
|
-
}) => Promise<{ input?: InputType } |
|
|
32
|
+
}) => Promise<{ input?: InputType } | undefined>;
|
|
28
33
|
|
|
29
34
|
getDefaultInput?: () => Promise<InputType>;
|
|
30
35
|
|
|
@@ -35,9 +40,11 @@ export type SlateAuthWithOauth<
|
|
|
35
40
|
clientId: string;
|
|
36
41
|
clientSecret: string;
|
|
37
42
|
scopes: string[];
|
|
43
|
+
config?: SlateAuthCallbackConfig;
|
|
38
44
|
}) => Promise<{
|
|
39
45
|
url: string;
|
|
40
46
|
input?: InputType;
|
|
47
|
+
callbackState?: Record<string, any>;
|
|
41
48
|
}>;
|
|
42
49
|
|
|
43
50
|
handleCallback: (ctx: {
|
|
@@ -48,9 +55,13 @@ export type SlateAuthWithOauth<
|
|
|
48
55
|
clientId: string;
|
|
49
56
|
clientSecret: string;
|
|
50
57
|
scopes: string[];
|
|
58
|
+
callbackParams?: Record<string, string>;
|
|
59
|
+
callbackState: Record<string, any>;
|
|
60
|
+
config?: SlateAuthCallbackConfig;
|
|
51
61
|
}) => Promise<{
|
|
52
62
|
output: OutputType;
|
|
53
63
|
input?: InputType;
|
|
64
|
+
scopes?: string[];
|
|
54
65
|
}>;
|
|
55
66
|
|
|
56
67
|
handleTokenRefresh?: (ctx: {
|
|
@@ -59,12 +70,18 @@ export type SlateAuthWithOauth<
|
|
|
59
70
|
clientId: string;
|
|
60
71
|
clientSecret: string;
|
|
61
72
|
scopes: string[];
|
|
73
|
+
config?: SlateAuthCallbackConfig;
|
|
62
74
|
}) => Promise<{
|
|
63
75
|
output: OutputType;
|
|
64
76
|
input?: InputType;
|
|
65
77
|
}>;
|
|
66
78
|
|
|
67
|
-
getProfile?: (ctx: {
|
|
79
|
+
getProfile?: (ctx: {
|
|
80
|
+
output: OutputType;
|
|
81
|
+
input: InputType;
|
|
82
|
+
scopes: string[];
|
|
83
|
+
config?: SlateAuthCallbackConfig;
|
|
84
|
+
}) => Promise<{
|
|
68
85
|
profile: Record<string, any>;
|
|
69
86
|
}>;
|
|
70
87
|
};
|
|
@@ -77,16 +94,26 @@ export type SlateAuthWithToken<InputType extends {}, OutputType extends { token?
|
|
|
77
94
|
|
|
78
95
|
inputSchema?: z.ZodType<InputType>;
|
|
79
96
|
|
|
80
|
-
|
|
97
|
+
docs?: SlateAuthDocsReference[];
|
|
98
|
+
|
|
99
|
+
getOutput: (ctx: { input: InputType; config?: SlateAuthCallbackConfig }) => Promise<{
|
|
100
|
+
output: OutputType;
|
|
101
|
+
scopes?: string[];
|
|
102
|
+
}>;
|
|
81
103
|
|
|
82
104
|
onInputChanged?: (params: {
|
|
83
105
|
previousInput: InputType;
|
|
84
106
|
newInput: InputType;
|
|
85
|
-
}) => Promise<{ input?: InputType } |
|
|
107
|
+
}) => Promise<{ input?: InputType } | undefined>;
|
|
86
108
|
|
|
87
109
|
getDefaultInput?: () => Promise<InputType>;
|
|
88
110
|
|
|
89
|
-
getProfile?: (ctx: {
|
|
111
|
+
getProfile?: (ctx: {
|
|
112
|
+
output: OutputType;
|
|
113
|
+
input: InputType;
|
|
114
|
+
scopes: string[];
|
|
115
|
+
config?: SlateAuthCallbackConfig;
|
|
116
|
+
}) => Promise<{
|
|
90
117
|
profile: Record<string, any>;
|
|
91
118
|
}>;
|
|
92
119
|
};
|
|
@@ -99,16 +126,26 @@ export type SlateAuthWithServiceAccount<InputType extends {}, OutputType extends
|
|
|
99
126
|
|
|
100
127
|
inputSchema?: z.ZodType<InputType>;
|
|
101
128
|
|
|
102
|
-
|
|
129
|
+
docs?: SlateAuthDocsReference[];
|
|
130
|
+
|
|
131
|
+
getOutput: (ctx: { input: InputType; config?: SlateAuthCallbackConfig }) => Promise<{
|
|
132
|
+
output: OutputType;
|
|
133
|
+
scopes?: string[];
|
|
134
|
+
}>;
|
|
103
135
|
|
|
104
136
|
onInputChanged?: (params: {
|
|
105
137
|
previousInput: InputType;
|
|
106
138
|
newInput: InputType;
|
|
107
|
-
}) => Promise<{ input?: InputType } |
|
|
139
|
+
}) => Promise<{ input?: InputType } | undefined>;
|
|
108
140
|
|
|
109
141
|
getDefaultInput?: () => Promise<InputType>;
|
|
110
142
|
|
|
111
|
-
getProfile?: (ctx: {
|
|
143
|
+
getProfile?: (ctx: {
|
|
144
|
+
output: OutputType;
|
|
145
|
+
input: InputType;
|
|
146
|
+
scopes: string[];
|
|
147
|
+
config?: SlateAuthCallbackConfig;
|
|
148
|
+
}) => Promise<{
|
|
112
149
|
profile: Record<string, any>;
|
|
113
150
|
}>;
|
|
114
151
|
};
|
|
@@ -121,21 +158,43 @@ export type SlateAuthWithCustomData<InputType extends {}, OutputType extends {}>
|
|
|
121
158
|
|
|
122
159
|
inputSchema?: z.ZodType<InputType>;
|
|
123
160
|
|
|
124
|
-
|
|
161
|
+
docs?: SlateAuthDocsReference[];
|
|
162
|
+
|
|
163
|
+
getOutput: (ctx: { input: InputType; config?: SlateAuthCallbackConfig }) => Promise<{
|
|
164
|
+
output: OutputType;
|
|
165
|
+
scopes?: string[];
|
|
166
|
+
}>;
|
|
167
|
+
|
|
168
|
+
handleTokenRefresh?: (ctx: {
|
|
169
|
+
output: OutputType;
|
|
170
|
+
input: InputType;
|
|
171
|
+
clientId: string;
|
|
172
|
+
clientSecret: string;
|
|
173
|
+
scopes: string[];
|
|
174
|
+
config?: SlateAuthCallbackConfig;
|
|
175
|
+
}) => Promise<{
|
|
176
|
+
output: OutputType;
|
|
177
|
+
input?: InputType;
|
|
178
|
+
}>;
|
|
125
179
|
|
|
126
180
|
onInputChanged?: (params: {
|
|
127
181
|
previousInput: InputType;
|
|
128
182
|
newInput: InputType;
|
|
129
|
-
}) => Promise<{ input?: InputType } |
|
|
183
|
+
}) => Promise<{ input?: InputType } | undefined>;
|
|
130
184
|
|
|
131
185
|
getDefaultInput?: () => Promise<InputType>;
|
|
132
186
|
|
|
133
|
-
getProfile?: (ctx: {
|
|
187
|
+
getProfile?: (ctx: {
|
|
188
|
+
output: OutputType;
|
|
189
|
+
input: InputType;
|
|
190
|
+
scopes: string[];
|
|
191
|
+
config?: SlateAuthCallbackConfig;
|
|
192
|
+
}) => Promise<{
|
|
134
193
|
profile: Record<string, any>;
|
|
135
194
|
}>;
|
|
136
195
|
};
|
|
137
196
|
|
|
138
|
-
export type SlateAuthWithNone<
|
|
197
|
+
export type SlateAuthWithNone<_InputType extends {}, _OutputType extends {}> = {
|
|
139
198
|
type: 'auth.none';
|
|
140
199
|
|
|
141
200
|
name: string;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { AxiosResponse } from 'axios';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { createAuthenticatedAxios, requestAxios, requestAxiosData } from './index';
|
|
4
|
+
|
|
5
|
+
describe('createAuthenticatedAxios', () => {
|
|
6
|
+
it('sets auth and JSON headers while preserving caller headers', () => {
|
|
7
|
+
let client = createAuthenticatedAxios({
|
|
8
|
+
baseURL: 'https://example.test',
|
|
9
|
+
authHeader: {
|
|
10
|
+
value: 'Bearer token'
|
|
11
|
+
},
|
|
12
|
+
headers: {
|
|
13
|
+
Accept: 'application/json'
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
expect(client.defaults.baseURL).toBe('https://example.test');
|
|
18
|
+
expect(client.defaults.headers.Authorization).toBe('Bearer token');
|
|
19
|
+
expect(client.defaults.headers['Content-Type']).toBe('application/json');
|
|
20
|
+
expect(client.defaults.headers.Accept).toBe('application/json');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('allows custom auth header names and disabling content type defaults', () => {
|
|
24
|
+
let client = createAuthenticatedAxios({
|
|
25
|
+
authHeader: {
|
|
26
|
+
name: 'X-API-Key',
|
|
27
|
+
value: 'secret'
|
|
28
|
+
},
|
|
29
|
+
contentType: false
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
expect(client.defaults.headers['X-API-Key']).toBe('secret');
|
|
33
|
+
expect(client.defaults.headers['Content-Type']).toBeUndefined();
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('requestAxios helpers', () => {
|
|
38
|
+
it('returns full responses or response data', async () => {
|
|
39
|
+
let response = {
|
|
40
|
+
config: {} as AxiosResponse<{ ok: boolean }>['config'],
|
|
41
|
+
data: { ok: true },
|
|
42
|
+
headers: {},
|
|
43
|
+
status: 200,
|
|
44
|
+
statusText: 'OK'
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
await expect(
|
|
48
|
+
requestAxios(
|
|
49
|
+
'get thing',
|
|
50
|
+
async () => response,
|
|
51
|
+
error => error
|
|
52
|
+
)
|
|
53
|
+
).resolves.toBe(response);
|
|
54
|
+
await expect(
|
|
55
|
+
requestAxiosData(
|
|
56
|
+
'get thing',
|
|
57
|
+
async () => response,
|
|
58
|
+
error => error
|
|
59
|
+
)
|
|
60
|
+
).resolves.toEqual({ ok: true });
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('maps failures with the operation name', async () => {
|
|
64
|
+
let upstream = new Error('upstream failed');
|
|
65
|
+
let mapped = new Error('mapped failure');
|
|
66
|
+
let seenOperation: string | undefined;
|
|
67
|
+
|
|
68
|
+
await expect(
|
|
69
|
+
requestAxiosData(
|
|
70
|
+
'create thing',
|
|
71
|
+
async () => {
|
|
72
|
+
throw upstream;
|
|
73
|
+
},
|
|
74
|
+
(_error, operation) => {
|
|
75
|
+
seenOperation = operation;
|
|
76
|
+
return mapped;
|
|
77
|
+
}
|
|
78
|
+
)
|
|
79
|
+
).rejects.toBe(mapped);
|
|
80
|
+
|
|
81
|
+
expect(seenOperation).toBe('create thing');
|
|
82
|
+
});
|
|
83
|
+
});
|