@slates/provider-handler 1.0.0-rc.40 → 1.0.0-rc.42
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 +124 -36
- package/dist/index.module.js +124 -36
- package/package.json +3 -3
- package/src/adminSetup.test.ts +189 -0
- package/src/index.ts +157 -35
- package/src/spec.test.ts +21 -0
- package/src/spec.ts +4 -1
package/dist/index.cjs
CHANGED
|
@@ -124,7 +124,8 @@ var mapAuthMethod = (slate, m) => ({
|
|
|
124
124
|
handleChangedInput: {
|
|
125
125
|
enabled: !!m.onInputChanged
|
|
126
126
|
},
|
|
127
|
-
getProfile: { enabled: !!m.getProfile }
|
|
127
|
+
getProfile: { enabled: !!m.getProfile },
|
|
128
|
+
..."adminSetup" in m && m.adminSetup ? { adminSetup: { enabled: true, required: m.adminSetup.required === true } } : {}
|
|
128
129
|
},
|
|
129
130
|
docs: m.docs ?? []
|
|
130
131
|
});
|
|
@@ -548,6 +549,57 @@ var createProviderHandler = (slate, listeners) => (0, import_proto.createSlatesP
|
|
|
548
549
|
let requestTraces = context.getHttpTraces();
|
|
549
550
|
return requestTraces.length > 0 ? { ...result, requestTraces } : result;
|
|
550
551
|
};
|
|
552
|
+
let buildAuthorizationUrlContext = (params) => ({
|
|
553
|
+
redirectUri: params.redirectUri,
|
|
554
|
+
state: params.state,
|
|
555
|
+
input: params.input,
|
|
556
|
+
clientId: params.clientId,
|
|
557
|
+
clientSecret: params.clientSecret,
|
|
558
|
+
scopes: params.scopes,
|
|
559
|
+
config: getAuthConfig()
|
|
560
|
+
});
|
|
561
|
+
let buildCallbackContext = (params) => ({
|
|
562
|
+
state: params.state,
|
|
563
|
+
redirectUri: params.redirectUri,
|
|
564
|
+
input: params.input,
|
|
565
|
+
clientId: params.clientId,
|
|
566
|
+
clientSecret: params.clientSecret,
|
|
567
|
+
scopes: params.scopes,
|
|
568
|
+
callbackParams: params.callbackParams || {},
|
|
569
|
+
callbackState: params.callbackState || {},
|
|
570
|
+
config: getAuthConfig()
|
|
571
|
+
});
|
|
572
|
+
let getTracedAuthorizationUrl = async (params, authenticationMethodName, trace, getAuthorizationUrl) => {
|
|
573
|
+
let context = getEmptyContext();
|
|
574
|
+
let urlRes = await traceProviderCall(
|
|
575
|
+
{
|
|
576
|
+
component: "auth",
|
|
577
|
+
functionName: trace.functionName,
|
|
578
|
+
message: trace.message,
|
|
579
|
+
successMessage: trace.successMessage,
|
|
580
|
+
metadata: {
|
|
581
|
+
authenticationMethodId: params.authenticationMethodId,
|
|
582
|
+
authenticationMethodName,
|
|
583
|
+
scopeCount: params.scopes.length,
|
|
584
|
+
inputKeyCount: getObjectKeyCount(params.input),
|
|
585
|
+
...trace.metadata
|
|
586
|
+
},
|
|
587
|
+
onSuccess: (result) => ({
|
|
588
|
+
returnedInput: !!result.input,
|
|
589
|
+
hasCallbackState: !!result.callbackState
|
|
590
|
+
})
|
|
591
|
+
},
|
|
592
|
+
() => (0, import_provider2.runWithContext)(
|
|
593
|
+
context,
|
|
594
|
+
() => getAuthorizationUrl(buildAuthorizationUrlContext(params))
|
|
595
|
+
)
|
|
596
|
+
);
|
|
597
|
+
return withRequestTraces(context, {
|
|
598
|
+
authorizationUrl: urlRes.url,
|
|
599
|
+
input: urlRes.input,
|
|
600
|
+
callbackState: urlRes.callbackState
|
|
601
|
+
});
|
|
602
|
+
};
|
|
551
603
|
manager.onNotification("slates/hello", async ({ params }) => {
|
|
552
604
|
protocol.set(params.protocol);
|
|
553
605
|
});
|
|
@@ -839,18 +891,7 @@ var createProviderHandler = (slate, listeners) => (0, import_proto.createSlatesP
|
|
|
839
891
|
},
|
|
840
892
|
() => (0, import_provider2.runWithContext)(
|
|
841
893
|
context,
|
|
842
|
-
() => authMethod.handleCallback({
|
|
843
|
-
code: params.code,
|
|
844
|
-
state: params.state,
|
|
845
|
-
redirectUri: params.redirectUri,
|
|
846
|
-
input: params.input,
|
|
847
|
-
clientId: params.clientId,
|
|
848
|
-
clientSecret: params.clientSecret,
|
|
849
|
-
scopes: params.scopes,
|
|
850
|
-
callbackParams: params.callbackParams || {},
|
|
851
|
-
callbackState: params.callbackState || {},
|
|
852
|
-
config: getAuthConfig()
|
|
853
|
-
})
|
|
894
|
+
() => authMethod.handleCallback({ ...buildCallbackContext(params), code: params.code })
|
|
854
895
|
)
|
|
855
896
|
);
|
|
856
897
|
return withRequestTraces(context, {
|
|
@@ -869,46 +910,93 @@ var createProviderHandler = (slate, listeners) => (0, import_proto.createSlatesP
|
|
|
869
910
|
getContextBasic();
|
|
870
911
|
let authMethod = getAuthMethod(slate, params.authenticationMethodId);
|
|
871
912
|
if ("getAuthorizationUrl" in authMethod) {
|
|
872
|
-
|
|
873
|
-
|
|
913
|
+
return getTracedAuthorizationUrl(
|
|
914
|
+
params,
|
|
915
|
+
authMethod.name,
|
|
874
916
|
{
|
|
875
|
-
component: "auth",
|
|
876
917
|
functionName: "getAuthorizationUrl",
|
|
877
918
|
message: "Getting authentication authorization URL",
|
|
878
|
-
successMessage: "Authentication authorization URL retrieved"
|
|
919
|
+
successMessage: "Authentication authorization URL retrieved"
|
|
920
|
+
},
|
|
921
|
+
(ctx) => authMethod.getAuthorizationUrl(ctx)
|
|
922
|
+
);
|
|
923
|
+
}
|
|
924
|
+
throw new import_error4.ServiceError(
|
|
925
|
+
(0, import_error4.preconditionFailedError)({
|
|
926
|
+
message: `Authentication method does not support authorization URL retrieval: ${params.authenticationMethodId}`
|
|
927
|
+
})
|
|
928
|
+
);
|
|
929
|
+
});
|
|
930
|
+
manager.onRequest("slates/auth.admin_authorization_url.get", async ({ params }) => {
|
|
931
|
+
getContextBasic();
|
|
932
|
+
let authMethod = getAuthMethod(slate, params.authenticationMethodId);
|
|
933
|
+
if (authMethod.type === "auth.oauth" && authMethod.adminSetup) {
|
|
934
|
+
let { adminSetup } = authMethod;
|
|
935
|
+
return getTracedAuthorizationUrl(
|
|
936
|
+
params,
|
|
937
|
+
authMethod.name,
|
|
938
|
+
{
|
|
939
|
+
functionName: "adminSetup.getAuthorizationUrl",
|
|
940
|
+
message: "Getting admin setup authorization URL",
|
|
941
|
+
successMessage: "Admin setup authorization URL retrieved",
|
|
942
|
+
metadata: { usesDefault: !adminSetup.getAuthorizationUrl }
|
|
943
|
+
},
|
|
944
|
+
(ctx) => adminSetup.getAuthorizationUrl ? adminSetup.getAuthorizationUrl(ctx) : authMethod.getAuthorizationUrl(ctx)
|
|
945
|
+
);
|
|
946
|
+
}
|
|
947
|
+
throw new import_error4.ServiceError(
|
|
948
|
+
(0, import_error4.preconditionFailedError)({
|
|
949
|
+
message: `Authentication method does not support admin setup: ${params.authenticationMethodId}`
|
|
950
|
+
})
|
|
951
|
+
);
|
|
952
|
+
});
|
|
953
|
+
manager.onRequest("slates/auth.admin_callback.handle", async ({ params }) => {
|
|
954
|
+
getContextBasic();
|
|
955
|
+
let authMethod = getAuthMethod(slate, params.authenticationMethodId);
|
|
956
|
+
if (authMethod.type === "auth.oauth" && authMethod.adminSetup) {
|
|
957
|
+
let { adminSetup } = authMethod;
|
|
958
|
+
let ctx = { ...buildCallbackContext(params), code: params.code };
|
|
959
|
+
let handleCallback = async () => {
|
|
960
|
+
if (adminSetup.handleCallback) return adminSetup.handleCallback(ctx);
|
|
961
|
+
let code = ctx.code;
|
|
962
|
+
if (!code) {
|
|
963
|
+
throw new import_error4.ServiceError(
|
|
964
|
+
(0, import_error4.badRequestError)({
|
|
965
|
+
message: `Admin setup callback did not include an authorization code: ${params.authenticationMethodId}`
|
|
966
|
+
})
|
|
967
|
+
);
|
|
968
|
+
}
|
|
969
|
+
await authMethod.handleCallback({ ...ctx, code });
|
|
970
|
+
return {};
|
|
971
|
+
};
|
|
972
|
+
let context = getEmptyContext();
|
|
973
|
+
let callbackRes = await traceProviderCall(
|
|
974
|
+
{
|
|
975
|
+
component: "auth",
|
|
976
|
+
functionName: "adminSetup.handleCallback",
|
|
977
|
+
message: "Handling admin setup callback",
|
|
978
|
+
successMessage: "Admin setup callback handled",
|
|
879
979
|
metadata: {
|
|
880
980
|
authenticationMethodId: params.authenticationMethodId,
|
|
881
981
|
authenticationMethodName: authMethod.name,
|
|
882
982
|
scopeCount: params.scopes.length,
|
|
883
|
-
|
|
983
|
+
hasCode: !!params.code,
|
|
984
|
+
hasCallbackState: !!params.callbackState,
|
|
985
|
+
usesDefault: !adminSetup.handleCallback
|
|
884
986
|
},
|
|
885
987
|
onSuccess: (result) => ({
|
|
886
|
-
|
|
887
|
-
hasCallbackState: !!result.callbackState
|
|
988
|
+
returnedInstructions: !!result.instructions
|
|
888
989
|
})
|
|
889
990
|
},
|
|
890
|
-
() => (0, import_provider2.runWithContext)(
|
|
891
|
-
context,
|
|
892
|
-
() => authMethod.getAuthorizationUrl({
|
|
893
|
-
redirectUri: params.redirectUri,
|
|
894
|
-
state: params.state,
|
|
895
|
-
input: params.input,
|
|
896
|
-
clientId: params.clientId,
|
|
897
|
-
clientSecret: params.clientSecret,
|
|
898
|
-
scopes: params.scopes,
|
|
899
|
-
config: getAuthConfig()
|
|
900
|
-
})
|
|
901
|
-
)
|
|
991
|
+
() => (0, import_provider2.runWithContext)(context, handleCallback)
|
|
902
992
|
);
|
|
903
993
|
return withRequestTraces(context, {
|
|
904
|
-
|
|
905
|
-
input: urlRes.input,
|
|
906
|
-
callbackState: urlRes.callbackState
|
|
994
|
+
instructions: callbackRes.instructions
|
|
907
995
|
});
|
|
908
996
|
}
|
|
909
997
|
throw new import_error4.ServiceError(
|
|
910
998
|
(0, import_error4.preconditionFailedError)({
|
|
911
|
-
message: `Authentication method does not support
|
|
999
|
+
message: `Authentication method does not support admin setup: ${params.authenticationMethodId}`
|
|
912
1000
|
})
|
|
913
1001
|
);
|
|
914
1002
|
});
|
package/dist/index.module.js
CHANGED
|
@@ -108,7 +108,8 @@ var mapAuthMethod = (slate, m) => ({
|
|
|
108
108
|
handleChangedInput: {
|
|
109
109
|
enabled: !!m.onInputChanged
|
|
110
110
|
},
|
|
111
|
-
getProfile: { enabled: !!m.getProfile }
|
|
111
|
+
getProfile: { enabled: !!m.getProfile },
|
|
112
|
+
..."adminSetup" in m && m.adminSetup ? { adminSetup: { enabled: true, required: m.adminSetup.required === true } } : {}
|
|
112
113
|
},
|
|
113
114
|
docs: m.docs ?? []
|
|
114
115
|
});
|
|
@@ -532,6 +533,57 @@ var createProviderHandler = (slate, listeners) => createSlatesProviderProtoHandl
|
|
|
532
533
|
let requestTraces = context.getHttpTraces();
|
|
533
534
|
return requestTraces.length > 0 ? { ...result, requestTraces } : result;
|
|
534
535
|
};
|
|
536
|
+
let buildAuthorizationUrlContext = (params) => ({
|
|
537
|
+
redirectUri: params.redirectUri,
|
|
538
|
+
state: params.state,
|
|
539
|
+
input: params.input,
|
|
540
|
+
clientId: params.clientId,
|
|
541
|
+
clientSecret: params.clientSecret,
|
|
542
|
+
scopes: params.scopes,
|
|
543
|
+
config: getAuthConfig()
|
|
544
|
+
});
|
|
545
|
+
let buildCallbackContext = (params) => ({
|
|
546
|
+
state: params.state,
|
|
547
|
+
redirectUri: params.redirectUri,
|
|
548
|
+
input: params.input,
|
|
549
|
+
clientId: params.clientId,
|
|
550
|
+
clientSecret: params.clientSecret,
|
|
551
|
+
scopes: params.scopes,
|
|
552
|
+
callbackParams: params.callbackParams || {},
|
|
553
|
+
callbackState: params.callbackState || {},
|
|
554
|
+
config: getAuthConfig()
|
|
555
|
+
});
|
|
556
|
+
let getTracedAuthorizationUrl = async (params, authenticationMethodName, trace, getAuthorizationUrl) => {
|
|
557
|
+
let context = getEmptyContext();
|
|
558
|
+
let urlRes = await traceProviderCall(
|
|
559
|
+
{
|
|
560
|
+
component: "auth",
|
|
561
|
+
functionName: trace.functionName,
|
|
562
|
+
message: trace.message,
|
|
563
|
+
successMessage: trace.successMessage,
|
|
564
|
+
metadata: {
|
|
565
|
+
authenticationMethodId: params.authenticationMethodId,
|
|
566
|
+
authenticationMethodName,
|
|
567
|
+
scopeCount: params.scopes.length,
|
|
568
|
+
inputKeyCount: getObjectKeyCount(params.input),
|
|
569
|
+
...trace.metadata
|
|
570
|
+
},
|
|
571
|
+
onSuccess: (result) => ({
|
|
572
|
+
returnedInput: !!result.input,
|
|
573
|
+
hasCallbackState: !!result.callbackState
|
|
574
|
+
})
|
|
575
|
+
},
|
|
576
|
+
() => runWithContext(
|
|
577
|
+
context,
|
|
578
|
+
() => getAuthorizationUrl(buildAuthorizationUrlContext(params))
|
|
579
|
+
)
|
|
580
|
+
);
|
|
581
|
+
return withRequestTraces(context, {
|
|
582
|
+
authorizationUrl: urlRes.url,
|
|
583
|
+
input: urlRes.input,
|
|
584
|
+
callbackState: urlRes.callbackState
|
|
585
|
+
});
|
|
586
|
+
};
|
|
535
587
|
manager.onNotification("slates/hello", async ({ params }) => {
|
|
536
588
|
protocol.set(params.protocol);
|
|
537
589
|
});
|
|
@@ -823,18 +875,7 @@ var createProviderHandler = (slate, listeners) => createSlatesProviderProtoHandl
|
|
|
823
875
|
},
|
|
824
876
|
() => runWithContext(
|
|
825
877
|
context,
|
|
826
|
-
() => authMethod.handleCallback({
|
|
827
|
-
code: params.code,
|
|
828
|
-
state: params.state,
|
|
829
|
-
redirectUri: params.redirectUri,
|
|
830
|
-
input: params.input,
|
|
831
|
-
clientId: params.clientId,
|
|
832
|
-
clientSecret: params.clientSecret,
|
|
833
|
-
scopes: params.scopes,
|
|
834
|
-
callbackParams: params.callbackParams || {},
|
|
835
|
-
callbackState: params.callbackState || {},
|
|
836
|
-
config: getAuthConfig()
|
|
837
|
-
})
|
|
878
|
+
() => authMethod.handleCallback({ ...buildCallbackContext(params), code: params.code })
|
|
838
879
|
)
|
|
839
880
|
);
|
|
840
881
|
return withRequestTraces(context, {
|
|
@@ -853,46 +894,93 @@ var createProviderHandler = (slate, listeners) => createSlatesProviderProtoHandl
|
|
|
853
894
|
getContextBasic();
|
|
854
895
|
let authMethod = getAuthMethod(slate, params.authenticationMethodId);
|
|
855
896
|
if ("getAuthorizationUrl" in authMethod) {
|
|
856
|
-
|
|
857
|
-
|
|
897
|
+
return getTracedAuthorizationUrl(
|
|
898
|
+
params,
|
|
899
|
+
authMethod.name,
|
|
858
900
|
{
|
|
859
|
-
component: "auth",
|
|
860
901
|
functionName: "getAuthorizationUrl",
|
|
861
902
|
message: "Getting authentication authorization URL",
|
|
862
|
-
successMessage: "Authentication authorization URL retrieved"
|
|
903
|
+
successMessage: "Authentication authorization URL retrieved"
|
|
904
|
+
},
|
|
905
|
+
(ctx) => authMethod.getAuthorizationUrl(ctx)
|
|
906
|
+
);
|
|
907
|
+
}
|
|
908
|
+
throw new ServiceError4(
|
|
909
|
+
preconditionFailedError({
|
|
910
|
+
message: `Authentication method does not support authorization URL retrieval: ${params.authenticationMethodId}`
|
|
911
|
+
})
|
|
912
|
+
);
|
|
913
|
+
});
|
|
914
|
+
manager.onRequest("slates/auth.admin_authorization_url.get", async ({ params }) => {
|
|
915
|
+
getContextBasic();
|
|
916
|
+
let authMethod = getAuthMethod(slate, params.authenticationMethodId);
|
|
917
|
+
if (authMethod.type === "auth.oauth" && authMethod.adminSetup) {
|
|
918
|
+
let { adminSetup } = authMethod;
|
|
919
|
+
return getTracedAuthorizationUrl(
|
|
920
|
+
params,
|
|
921
|
+
authMethod.name,
|
|
922
|
+
{
|
|
923
|
+
functionName: "adminSetup.getAuthorizationUrl",
|
|
924
|
+
message: "Getting admin setup authorization URL",
|
|
925
|
+
successMessage: "Admin setup authorization URL retrieved",
|
|
926
|
+
metadata: { usesDefault: !adminSetup.getAuthorizationUrl }
|
|
927
|
+
},
|
|
928
|
+
(ctx) => adminSetup.getAuthorizationUrl ? adminSetup.getAuthorizationUrl(ctx) : authMethod.getAuthorizationUrl(ctx)
|
|
929
|
+
);
|
|
930
|
+
}
|
|
931
|
+
throw new ServiceError4(
|
|
932
|
+
preconditionFailedError({
|
|
933
|
+
message: `Authentication method does not support admin setup: ${params.authenticationMethodId}`
|
|
934
|
+
})
|
|
935
|
+
);
|
|
936
|
+
});
|
|
937
|
+
manager.onRequest("slates/auth.admin_callback.handle", async ({ params }) => {
|
|
938
|
+
getContextBasic();
|
|
939
|
+
let authMethod = getAuthMethod(slate, params.authenticationMethodId);
|
|
940
|
+
if (authMethod.type === "auth.oauth" && authMethod.adminSetup) {
|
|
941
|
+
let { adminSetup } = authMethod;
|
|
942
|
+
let ctx = { ...buildCallbackContext(params), code: params.code };
|
|
943
|
+
let handleCallback = async () => {
|
|
944
|
+
if (adminSetup.handleCallback) return adminSetup.handleCallback(ctx);
|
|
945
|
+
let code = ctx.code;
|
|
946
|
+
if (!code) {
|
|
947
|
+
throw new ServiceError4(
|
|
948
|
+
badRequestError3({
|
|
949
|
+
message: `Admin setup callback did not include an authorization code: ${params.authenticationMethodId}`
|
|
950
|
+
})
|
|
951
|
+
);
|
|
952
|
+
}
|
|
953
|
+
await authMethod.handleCallback({ ...ctx, code });
|
|
954
|
+
return {};
|
|
955
|
+
};
|
|
956
|
+
let context = getEmptyContext();
|
|
957
|
+
let callbackRes = await traceProviderCall(
|
|
958
|
+
{
|
|
959
|
+
component: "auth",
|
|
960
|
+
functionName: "adminSetup.handleCallback",
|
|
961
|
+
message: "Handling admin setup callback",
|
|
962
|
+
successMessage: "Admin setup callback handled",
|
|
863
963
|
metadata: {
|
|
864
964
|
authenticationMethodId: params.authenticationMethodId,
|
|
865
965
|
authenticationMethodName: authMethod.name,
|
|
866
966
|
scopeCount: params.scopes.length,
|
|
867
|
-
|
|
967
|
+
hasCode: !!params.code,
|
|
968
|
+
hasCallbackState: !!params.callbackState,
|
|
969
|
+
usesDefault: !adminSetup.handleCallback
|
|
868
970
|
},
|
|
869
971
|
onSuccess: (result) => ({
|
|
870
|
-
|
|
871
|
-
hasCallbackState: !!result.callbackState
|
|
972
|
+
returnedInstructions: !!result.instructions
|
|
872
973
|
})
|
|
873
974
|
},
|
|
874
|
-
() => runWithContext(
|
|
875
|
-
context,
|
|
876
|
-
() => authMethod.getAuthorizationUrl({
|
|
877
|
-
redirectUri: params.redirectUri,
|
|
878
|
-
state: params.state,
|
|
879
|
-
input: params.input,
|
|
880
|
-
clientId: params.clientId,
|
|
881
|
-
clientSecret: params.clientSecret,
|
|
882
|
-
scopes: params.scopes,
|
|
883
|
-
config: getAuthConfig()
|
|
884
|
-
})
|
|
885
|
-
)
|
|
975
|
+
() => runWithContext(context, handleCallback)
|
|
886
976
|
);
|
|
887
977
|
return withRequestTraces(context, {
|
|
888
|
-
|
|
889
|
-
input: urlRes.input,
|
|
890
|
-
callbackState: urlRes.callbackState
|
|
978
|
+
instructions: callbackRes.instructions
|
|
891
979
|
});
|
|
892
980
|
}
|
|
893
981
|
throw new ServiceError4(
|
|
894
982
|
preconditionFailedError({
|
|
895
|
-
message: `Authentication method does not support
|
|
983
|
+
message: `Authentication method does not support admin setup: ${params.authenticationMethodId}`
|
|
896
984
|
})
|
|
897
985
|
);
|
|
898
986
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slates/provider-handler",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.42",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@lowerdeck/error": "^1.1.0",
|
|
35
|
-
"@slates/proto": "1.0.0-rc.
|
|
36
|
-
"@slates/provider": "1.0.0-rc.
|
|
35
|
+
"@slates/proto": "1.0.0-rc.27",
|
|
36
|
+
"@slates/provider": "1.0.0-rc.32",
|
|
37
37
|
"p-queue": "^6.6.2",
|
|
38
38
|
"zod": "^4.2.1"
|
|
39
39
|
},
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { SLATES_PROTOCOL_VERSION, SlatesProviderProtoHandlerManager } from '@slates/proto';
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { createProviderHandler } from './index';
|
|
5
|
+
|
|
6
|
+
let createManager = async (method: Record<string, any>) => {
|
|
7
|
+
let slate = {
|
|
8
|
+
spec: {
|
|
9
|
+
key: 'test',
|
|
10
|
+
name: 'Test',
|
|
11
|
+
auth: {
|
|
12
|
+
authStack: [
|
|
13
|
+
{
|
|
14
|
+
type: 'auth.oauth',
|
|
15
|
+
key: 'oauth',
|
|
16
|
+
name: 'OAuth',
|
|
17
|
+
scopes: [],
|
|
18
|
+
...method
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
outputSchema: z.object({ token: z.string() })
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
adapters: [],
|
|
25
|
+
actions: [],
|
|
26
|
+
triggerGroups: []
|
|
27
|
+
};
|
|
28
|
+
let manager = await createProviderHandler(slate as any, []).run();
|
|
29
|
+
|
|
30
|
+
await SlatesProviderProtoHandlerManager.handleInput(manager, {
|
|
31
|
+
jsonrpc: '2.0',
|
|
32
|
+
method: 'slates/hello',
|
|
33
|
+
params: { protocol: SLATES_PROTOCOL_VERSION }
|
|
34
|
+
});
|
|
35
|
+
await SlatesProviderProtoHandlerManager.handleInput(manager, {
|
|
36
|
+
jsonrpc: '2.0',
|
|
37
|
+
method: 'slates/participant.set',
|
|
38
|
+
params: { participants: [] }
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
return manager;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
let baseParams = {
|
|
45
|
+
authenticationMethodId: 'oauth',
|
|
46
|
+
state: 'state-123',
|
|
47
|
+
redirectUri: 'https://hub.example/callback',
|
|
48
|
+
input: { tenant: 'acme' },
|
|
49
|
+
clientId: 'client-id',
|
|
50
|
+
clientSecret: 'client-secret',
|
|
51
|
+
scopes: ['read']
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
let getAdminAuthorizationUrl = (manager: SlatesProviderProtoHandlerManager) =>
|
|
55
|
+
SlatesProviderProtoHandlerManager.handleInput(manager, {
|
|
56
|
+
jsonrpc: '2.0',
|
|
57
|
+
id: 'url',
|
|
58
|
+
method: 'slates/auth.admin_authorization_url.get',
|
|
59
|
+
params: baseParams
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
let handleAdminCallback = (
|
|
63
|
+
manager: SlatesProviderProtoHandlerManager,
|
|
64
|
+
params: { code?: string; callbackParams?: Record<string, string> } = {}
|
|
65
|
+
) =>
|
|
66
|
+
SlatesProviderProtoHandlerManager.handleInput(manager, {
|
|
67
|
+
jsonrpc: '2.0',
|
|
68
|
+
id: 'callback',
|
|
69
|
+
method: 'slates/auth.admin_callback.handle',
|
|
70
|
+
params: { ...baseParams, ...params }
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
let regularMethod = () => ({
|
|
74
|
+
getAuthorizationUrl: vi.fn(async () => ({
|
|
75
|
+
url: 'https://provider.example/authorize',
|
|
76
|
+
callbackState: { verifier: 'regular' }
|
|
77
|
+
})),
|
|
78
|
+
handleCallback: vi.fn(async () => ({ output: { token: 'user-token' } }))
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe('slates/auth.admin_authorization_url.get', () => {
|
|
82
|
+
it('falls back to the regular authorization URL', async () => {
|
|
83
|
+
let method = regularMethod();
|
|
84
|
+
let manager = await createManager({ ...method, adminSetup: {} });
|
|
85
|
+
|
|
86
|
+
expect(await getAdminAuthorizationUrl(manager)).toEqual({
|
|
87
|
+
jsonrpc: '2.0',
|
|
88
|
+
id: 'url',
|
|
89
|
+
result: {
|
|
90
|
+
authorizationUrl: 'https://provider.example/authorize',
|
|
91
|
+
callbackState: { verifier: 'regular' }
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
let { authenticationMethodId: _, ...ctx } = baseParams;
|
|
95
|
+
expect(method.getAuthorizationUrl).toHaveBeenCalledWith({ ...ctx, config: {} });
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('uses the admin authorization URL override', async () => {
|
|
99
|
+
let method = regularMethod();
|
|
100
|
+
let adminGetAuthorizationUrl = vi.fn(async () => ({
|
|
101
|
+
url: 'https://provider.example/adminconsent',
|
|
102
|
+
input: { tenant: 'acme' }
|
|
103
|
+
}));
|
|
104
|
+
let manager = await createManager({
|
|
105
|
+
...method,
|
|
106
|
+
adminSetup: { getAuthorizationUrl: adminGetAuthorizationUrl }
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
expect(await getAdminAuthorizationUrl(manager)).toMatchObject({
|
|
110
|
+
result: {
|
|
111
|
+
authorizationUrl: 'https://provider.example/adminconsent',
|
|
112
|
+
input: { tenant: 'acme' }
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
expect(adminGetAuthorizationUrl).toHaveBeenCalledWith(
|
|
116
|
+
expect.objectContaining({ clientId: 'client-id', state: 'state-123', config: {} })
|
|
117
|
+
);
|
|
118
|
+
expect(method.getAuthorizationUrl).not.toHaveBeenCalled();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('rejects methods without admin setup', async () => {
|
|
122
|
+
let manager = await createManager(regularMethod());
|
|
123
|
+
|
|
124
|
+
expect(await getAdminAuthorizationUrl(manager)).toMatchObject({
|
|
125
|
+
error: { code: 'request.precondition_failed' }
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
describe('slates/auth.admin_callback.handle', () => {
|
|
131
|
+
it('runs the regular callback and discards its output by default', async () => {
|
|
132
|
+
let method = regularMethod();
|
|
133
|
+
let manager = await createManager({ ...method, adminSetup: {} });
|
|
134
|
+
|
|
135
|
+
expect(await handleAdminCallback(manager, { code: 'admin-code' })).toEqual({
|
|
136
|
+
jsonrpc: '2.0',
|
|
137
|
+
id: 'callback',
|
|
138
|
+
result: {}
|
|
139
|
+
});
|
|
140
|
+
expect(method.handleCallback).toHaveBeenCalledWith(
|
|
141
|
+
expect.objectContaining({
|
|
142
|
+
code: 'admin-code',
|
|
143
|
+
clientId: 'client-id',
|
|
144
|
+
callbackParams: {},
|
|
145
|
+
callbackState: {},
|
|
146
|
+
config: {}
|
|
147
|
+
})
|
|
148
|
+
);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('requires a code when falling back to the regular callback', async () => {
|
|
152
|
+
let method = regularMethod();
|
|
153
|
+
let manager = await createManager({ ...method, adminSetup: {} });
|
|
154
|
+
|
|
155
|
+
expect(await handleAdminCallback(manager)).toMatchObject({
|
|
156
|
+
error: { code: 'request.bad', message: expect.stringContaining('authorization code') }
|
|
157
|
+
});
|
|
158
|
+
expect(method.handleCallback).not.toHaveBeenCalled();
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('uses the admin callback override without a code', async () => {
|
|
162
|
+
let method = regularMethod();
|
|
163
|
+
let adminHandleCallback = vi.fn(async () => ({ instructions: '1. Trust the app.' }));
|
|
164
|
+
let manager = await createManager({
|
|
165
|
+
...method,
|
|
166
|
+
adminSetup: { handleCallback: adminHandleCallback }
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
expect(
|
|
170
|
+
await handleAdminCallback(manager, { callbackParams: { admin_consent: 'True' } })
|
|
171
|
+
).toMatchObject({ result: { instructions: '1. Trust the app.' } });
|
|
172
|
+
expect(adminHandleCallback).toHaveBeenCalledWith(
|
|
173
|
+
expect.objectContaining({
|
|
174
|
+
code: undefined,
|
|
175
|
+
callbackParams: { admin_consent: 'True' },
|
|
176
|
+
callbackState: {}
|
|
177
|
+
})
|
|
178
|
+
);
|
|
179
|
+
expect(method.handleCallback).not.toHaveBeenCalled();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('rejects methods without admin setup', async () => {
|
|
183
|
+
let manager = await createManager(regularMethod());
|
|
184
|
+
|
|
185
|
+
expect(await handleAdminCallback(manager, { code: 'admin-code' })).toMatchObject({
|
|
186
|
+
error: { code: 'request.precondition_failed' }
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
import {
|
|
8
8
|
createSlatesProviderProtoHandler,
|
|
9
9
|
SLATES_PROTOCOL_VERSION,
|
|
10
|
+
type SlatesMessageAuthAdminCallbackHandleRequest,
|
|
11
|
+
type SlatesMessageAuthAuthorizationUrlGetRequest,
|
|
10
12
|
type SlatesParticipant
|
|
11
13
|
} from '@slates/proto';
|
|
12
14
|
import {
|
|
@@ -344,6 +346,77 @@ export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
|
|
|
344
346
|
return requestTraces.length > 0 ? { ...result, requestTraces } : result;
|
|
345
347
|
};
|
|
346
348
|
|
|
349
|
+
let buildAuthorizationUrlContext = (
|
|
350
|
+
params: SlatesMessageAuthAuthorizationUrlGetRequest['params']
|
|
351
|
+
) => ({
|
|
352
|
+
redirectUri: params.redirectUri,
|
|
353
|
+
state: params.state,
|
|
354
|
+
input: params.input,
|
|
355
|
+
clientId: params.clientId,
|
|
356
|
+
clientSecret: params.clientSecret,
|
|
357
|
+
scopes: params.scopes,
|
|
358
|
+
config: getAuthConfig()
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
let buildCallbackContext = (
|
|
362
|
+
params: Omit<SlatesMessageAuthAdminCallbackHandleRequest['params'], 'code'>
|
|
363
|
+
) => ({
|
|
364
|
+
state: params.state,
|
|
365
|
+
redirectUri: params.redirectUri,
|
|
366
|
+
input: params.input,
|
|
367
|
+
clientId: params.clientId,
|
|
368
|
+
clientSecret: params.clientSecret,
|
|
369
|
+
scopes: params.scopes,
|
|
370
|
+
callbackParams: params.callbackParams || {},
|
|
371
|
+
callbackState: params.callbackState || {},
|
|
372
|
+
config: getAuthConfig()
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
let getTracedAuthorizationUrl = async (
|
|
376
|
+
params: SlatesMessageAuthAuthorizationUrlGetRequest['params'],
|
|
377
|
+
authenticationMethodName: string,
|
|
378
|
+
trace: {
|
|
379
|
+
functionName: string;
|
|
380
|
+
message: string;
|
|
381
|
+
successMessage: string;
|
|
382
|
+
metadata?: Record<string, unknown>;
|
|
383
|
+
},
|
|
384
|
+
getAuthorizationUrl: (
|
|
385
|
+
ctx: ReturnType<typeof buildAuthorizationUrlContext>
|
|
386
|
+
) => Promise<{ url: string; input?: any; callbackState?: Record<string, any> }>
|
|
387
|
+
) => {
|
|
388
|
+
let context = getEmptyContext();
|
|
389
|
+
let urlRes = await traceProviderCall(
|
|
390
|
+
{
|
|
391
|
+
component: 'auth',
|
|
392
|
+
functionName: trace.functionName,
|
|
393
|
+
message: trace.message,
|
|
394
|
+
successMessage: trace.successMessage,
|
|
395
|
+
metadata: {
|
|
396
|
+
authenticationMethodId: params.authenticationMethodId,
|
|
397
|
+
authenticationMethodName,
|
|
398
|
+
scopeCount: params.scopes.length,
|
|
399
|
+
inputKeyCount: getObjectKeyCount(params.input),
|
|
400
|
+
...trace.metadata
|
|
401
|
+
},
|
|
402
|
+
onSuccess: result => ({
|
|
403
|
+
returnedInput: !!result.input,
|
|
404
|
+
hasCallbackState: !!result.callbackState
|
|
405
|
+
})
|
|
406
|
+
},
|
|
407
|
+
() =>
|
|
408
|
+
runWithContext(context, () =>
|
|
409
|
+
getAuthorizationUrl(buildAuthorizationUrlContext(params))
|
|
410
|
+
)
|
|
411
|
+
);
|
|
412
|
+
|
|
413
|
+
return withRequestTraces(context, {
|
|
414
|
+
authorizationUrl: urlRes.url,
|
|
415
|
+
input: urlRes.input,
|
|
416
|
+
callbackState: urlRes.callbackState
|
|
417
|
+
});
|
|
418
|
+
};
|
|
419
|
+
|
|
347
420
|
manager.onNotification('slates/hello', async ({ params }) => {
|
|
348
421
|
protocol.set(params.protocol);
|
|
349
422
|
});
|
|
@@ -681,18 +754,7 @@ export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
|
|
|
681
754
|
},
|
|
682
755
|
() =>
|
|
683
756
|
runWithContext(context, () =>
|
|
684
|
-
authMethod.handleCallback({
|
|
685
|
-
code: params.code,
|
|
686
|
-
state: params.state,
|
|
687
|
-
redirectUri: params.redirectUri,
|
|
688
|
-
input: params.input,
|
|
689
|
-
clientId: params.clientId,
|
|
690
|
-
clientSecret: params.clientSecret,
|
|
691
|
-
scopes: params.scopes,
|
|
692
|
-
callbackParams: params.callbackParams || {},
|
|
693
|
-
callbackState: params.callbackState || {},
|
|
694
|
-
config: getAuthConfig()
|
|
695
|
-
})
|
|
757
|
+
authMethod.handleCallback({ ...buildCallbackContext(params), code: params.code })
|
|
696
758
|
)
|
|
697
759
|
);
|
|
698
760
|
|
|
@@ -715,48 +777,108 @@ export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
|
|
|
715
777
|
let authMethod = getAuthMethod(slate, params.authenticationMethodId);
|
|
716
778
|
|
|
717
779
|
if ('getAuthorizationUrl' in authMethod) {
|
|
718
|
-
|
|
719
|
-
|
|
780
|
+
return getTracedAuthorizationUrl(
|
|
781
|
+
params,
|
|
782
|
+
authMethod.name,
|
|
720
783
|
{
|
|
721
|
-
component: 'auth',
|
|
722
784
|
functionName: 'getAuthorizationUrl',
|
|
723
785
|
message: 'Getting authentication authorization URL',
|
|
724
|
-
successMessage: 'Authentication authorization URL retrieved'
|
|
786
|
+
successMessage: 'Authentication authorization URL retrieved'
|
|
787
|
+
},
|
|
788
|
+
ctx => authMethod.getAuthorizationUrl(ctx)
|
|
789
|
+
);
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
throw new ServiceError(
|
|
793
|
+
preconditionFailedError({
|
|
794
|
+
message: `Authentication method does not support authorization URL retrieval: ${params.authenticationMethodId}`
|
|
795
|
+
})
|
|
796
|
+
);
|
|
797
|
+
});
|
|
798
|
+
|
|
799
|
+
manager.onRequest('slates/auth.admin_authorization_url.get', async ({ params }) => {
|
|
800
|
+
getContextBasic();
|
|
801
|
+
let authMethod = getAuthMethod(slate, params.authenticationMethodId);
|
|
802
|
+
|
|
803
|
+
if (authMethod.type === 'auth.oauth' && authMethod.adminSetup) {
|
|
804
|
+
let { adminSetup } = authMethod;
|
|
805
|
+
return getTracedAuthorizationUrl(
|
|
806
|
+
params,
|
|
807
|
+
authMethod.name,
|
|
808
|
+
{
|
|
809
|
+
functionName: 'adminSetup.getAuthorizationUrl',
|
|
810
|
+
message: 'Getting admin setup authorization URL',
|
|
811
|
+
successMessage: 'Admin setup authorization URL retrieved',
|
|
812
|
+
metadata: { usesDefault: !adminSetup.getAuthorizationUrl }
|
|
813
|
+
},
|
|
814
|
+
ctx =>
|
|
815
|
+
adminSetup.getAuthorizationUrl
|
|
816
|
+
? adminSetup.getAuthorizationUrl(ctx)
|
|
817
|
+
: authMethod.getAuthorizationUrl(ctx)
|
|
818
|
+
);
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
throw new ServiceError(
|
|
822
|
+
preconditionFailedError({
|
|
823
|
+
message: `Authentication method does not support admin setup: ${params.authenticationMethodId}`
|
|
824
|
+
})
|
|
825
|
+
);
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
manager.onRequest('slates/auth.admin_callback.handle', async ({ params }) => {
|
|
829
|
+
getContextBasic();
|
|
830
|
+
let authMethod = getAuthMethod(slate, params.authenticationMethodId);
|
|
831
|
+
|
|
832
|
+
if (authMethod.type === 'auth.oauth' && authMethod.adminSetup) {
|
|
833
|
+
let { adminSetup } = authMethod;
|
|
834
|
+
let ctx = { ...buildCallbackContext(params), code: params.code };
|
|
835
|
+
|
|
836
|
+
let handleCallback = async (): Promise<{ instructions?: string }> => {
|
|
837
|
+
if (adminSetup.handleCallback) return adminSetup.handleCallback(ctx);
|
|
838
|
+
|
|
839
|
+
let code = ctx.code;
|
|
840
|
+
if (!code) {
|
|
841
|
+
throw new ServiceError(
|
|
842
|
+
badRequestError({
|
|
843
|
+
message: `Admin setup callback did not include an authorization code: ${params.authenticationMethodId}`
|
|
844
|
+
})
|
|
845
|
+
);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
await authMethod.handleCallback({ ...ctx, code });
|
|
849
|
+
return {};
|
|
850
|
+
};
|
|
851
|
+
|
|
852
|
+
let context = getEmptyContext();
|
|
853
|
+
let callbackRes = await traceProviderCall(
|
|
854
|
+
{
|
|
855
|
+
component: 'auth',
|
|
856
|
+
functionName: 'adminSetup.handleCallback',
|
|
857
|
+
message: 'Handling admin setup callback',
|
|
858
|
+
successMessage: 'Admin setup callback handled',
|
|
725
859
|
metadata: {
|
|
726
860
|
authenticationMethodId: params.authenticationMethodId,
|
|
727
861
|
authenticationMethodName: authMethod.name,
|
|
728
862
|
scopeCount: params.scopes.length,
|
|
729
|
-
|
|
863
|
+
hasCode: !!params.code,
|
|
864
|
+
hasCallbackState: !!params.callbackState,
|
|
865
|
+
usesDefault: !adminSetup.handleCallback
|
|
730
866
|
},
|
|
731
867
|
onSuccess: result => ({
|
|
732
|
-
|
|
733
|
-
hasCallbackState: !!result.callbackState
|
|
868
|
+
returnedInstructions: !!result.instructions
|
|
734
869
|
})
|
|
735
870
|
},
|
|
736
|
-
() =>
|
|
737
|
-
runWithContext(context, () =>
|
|
738
|
-
authMethod.getAuthorizationUrl({
|
|
739
|
-
redirectUri: params.redirectUri,
|
|
740
|
-
state: params.state,
|
|
741
|
-
input: params.input,
|
|
742
|
-
clientId: params.clientId,
|
|
743
|
-
clientSecret: params.clientSecret,
|
|
744
|
-
scopes: params.scopes,
|
|
745
|
-
config: getAuthConfig()
|
|
746
|
-
})
|
|
747
|
-
)
|
|
871
|
+
() => runWithContext(context, handleCallback)
|
|
748
872
|
);
|
|
749
873
|
|
|
750
874
|
return withRequestTraces(context, {
|
|
751
|
-
|
|
752
|
-
input: urlRes.input,
|
|
753
|
-
callbackState: urlRes.callbackState
|
|
875
|
+
instructions: callbackRes.instructions
|
|
754
876
|
});
|
|
755
877
|
}
|
|
756
878
|
|
|
757
879
|
throw new ServiceError(
|
|
758
880
|
preconditionFailedError({
|
|
759
|
-
message: `Authentication method does not support
|
|
881
|
+
message: `Authentication method does not support admin setup: ${params.authenticationMethodId}`
|
|
760
882
|
})
|
|
761
883
|
);
|
|
762
884
|
});
|
package/src/spec.test.ts
CHANGED
|
@@ -222,4 +222,25 @@ describe('mapAuthMethod', () => {
|
|
|
222
222
|
expect(mapAuthMethod(slate, method)).not.toHaveProperty('syncTokensAcrossConnections');
|
|
223
223
|
}
|
|
224
224
|
});
|
|
225
|
+
|
|
226
|
+
it('omits the adminSetup capability when undeclared so existing spec hashes stay stable', () => {
|
|
227
|
+
expect(
|
|
228
|
+
mapAuthMethod(slate, { ...oauthMethod, adminSetup: {} }).capabilities
|
|
229
|
+
).toMatchObject({ adminSetup: { enabled: true } });
|
|
230
|
+
expect(mapAuthMethod(slate, oauthMethod).capabilities).not.toHaveProperty('adminSetup');
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('always advertises whether admin setup is required, defaulting to false', () => {
|
|
234
|
+
expect(
|
|
235
|
+
mapAuthMethod(slate, { ...oauthMethod, adminSetup: {} }).capabilities.adminSetup
|
|
236
|
+
).toEqual({ enabled: true, required: false });
|
|
237
|
+
expect(
|
|
238
|
+
mapAuthMethod(slate, { ...oauthMethod, adminSetup: { required: false } }).capabilities
|
|
239
|
+
.adminSetup
|
|
240
|
+
).toEqual({ enabled: true, required: false });
|
|
241
|
+
expect(
|
|
242
|
+
mapAuthMethod(slate, { ...oauthMethod, adminSetup: { required: true } }).capabilities
|
|
243
|
+
.adminSetup
|
|
244
|
+
).toEqual({ enabled: true, required: true });
|
|
245
|
+
});
|
|
225
246
|
});
|
package/src/spec.ts
CHANGED
|
@@ -66,7 +66,10 @@ export let mapAuthMethod = <ConfigType extends {}, AuthType extends {}>(
|
|
|
66
66
|
handleChangedInput: {
|
|
67
67
|
enabled: !!m.onInputChanged
|
|
68
68
|
},
|
|
69
|
-
getProfile: { enabled: !!m.getProfile }
|
|
69
|
+
getProfile: { enabled: !!m.getProfile },
|
|
70
|
+
...('adminSetup' in m && m.adminSetup
|
|
71
|
+
? { adminSetup: { enabled: true, required: m.adminSetup.required === true } }
|
|
72
|
+
: {})
|
|
70
73
|
},
|
|
71
74
|
|
|
72
75
|
docs: m.docs ?? []
|