@crombolo/api-delivery 0.0.0-stage → 0.13.1

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.
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Umbraco's `DeliveryApiSettings`, with its defaults: off until a site turns
3
+ * it on. docs/delivery-api.md, "Settings".
4
+ */
5
+
6
+ export interface DeliveryApiClientMember {
7
+ /** The member this client signs in as. */
8
+ userName: string
9
+ /** Sent as `umbraco-member-<clientId>`; the prefix is added when missing. */
10
+ clientId: string
11
+ clientSecret: string
12
+ }
13
+
14
+ export interface DeliveryApiSettings {
15
+ enabled: boolean
16
+ /** False: every content request needs the API key. */
17
+ publicAccess: boolean
18
+ /** Unlocks non-public access and preview. Unset, nothing can preview. */
19
+ apiKey: string | undefined
20
+ /** When any are listed, only these content types are served. */
21
+ allowedContentTypeAliases: string[]
22
+ /** Never served; ignored when the allow list has entries. */
23
+ disallowedContentTypeAliases: string[]
24
+ media: { enabled: boolean; publicAccess: boolean }
25
+ memberAuthorization: {
26
+ authorizationCodeFlow: {
27
+ enabled: boolean
28
+ /** Absolute URLs an app may be sent back to after signing in. */
29
+ loginRedirectUrls: string[]
30
+ logoutRedirectUrls: string[]
31
+ /**
32
+ * The site page holding the member login form, e.g. `/login`. A member who
33
+ * is not signed in is sent there with `returnUrl`, and comes back to finish.
34
+ */
35
+ loginPage: string | undefined
36
+ }
37
+ clientCredentialsFlow: { enabled: boolean; associatedMembers: DeliveryApiClientMember[] }
38
+ /** Umbraco's `Global:TimeOut`: a refresh token lasts this long, an access token a quarter of it. */
39
+ timeoutMinutes: number
40
+ }
41
+ }
42
+
43
+ type DeepPartial<T> = { [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K] }
44
+
45
+ /** What a site may set: any part of the settings. */
46
+ export type DeliveryApiOptions = DeepPartial<DeliveryApiSettings>
47
+
48
+ export const CLIENT_ID_PREFIX = 'umbraco-member-'
49
+
50
+ /** The client id a caller sends for a configured member client. Pure. */
51
+ export const memberClientId = (id: string): string =>
52
+ id.startsWith(CLIENT_ID_PREFIX) ? id : `${CLIENT_ID_PREFIX}${id}`
53
+
54
+ /** The settings with every default filled in. Pure. */
55
+ export function resolveDeliveryApiSettings(options: DeliveryApiOptions = {}): DeliveryApiSettings {
56
+ const code = options.memberAuthorization?.authorizationCodeFlow ?? {}
57
+ const credentials = options.memberAuthorization?.clientCredentialsFlow ?? {}
58
+ return {
59
+ enabled: options.enabled ?? false,
60
+ publicAccess: options.publicAccess ?? true,
61
+ apiKey: options.apiKey?.trim() ? options.apiKey : undefined,
62
+ allowedContentTypeAliases: [...(options.allowedContentTypeAliases ?? [])].filter(
63
+ (alias): alias is string => typeof alias === 'string',
64
+ ),
65
+ disallowedContentTypeAliases: [...(options.disallowedContentTypeAliases ?? [])].filter(
66
+ (alias): alias is string => typeof alias === 'string',
67
+ ),
68
+ media: {
69
+ enabled: options.media?.enabled ?? false,
70
+ publicAccess: options.media?.publicAccess ?? true,
71
+ },
72
+ memberAuthorization: {
73
+ authorizationCodeFlow: {
74
+ enabled: code.enabled ?? false,
75
+ loginRedirectUrls: [...(code.loginRedirectUrls ?? [])].filter(
76
+ (url): url is string => typeof url === 'string',
77
+ ),
78
+ logoutRedirectUrls: [...(code.logoutRedirectUrls ?? [])].filter(
79
+ (url): url is string => typeof url === 'string',
80
+ ),
81
+ loginPage: code.loginPage,
82
+ },
83
+ clientCredentialsFlow: {
84
+ enabled: credentials.enabled ?? false,
85
+ associatedMembers: [...(credentials.associatedMembers ?? [])].flatMap((member) =>
86
+ member?.userName && member.clientId && member.clientSecret
87
+ ? [
88
+ {
89
+ userName: member.userName,
90
+ clientId: memberClientId(member.clientId),
91
+ clientSecret: member.clientSecret,
92
+ },
93
+ ]
94
+ : [],
95
+ ),
96
+ },
97
+ timeoutMinutes: options.memberAuthorization?.timeoutMinutes ?? 20,
98
+ },
99
+ }
100
+ }
101
+
102
+ /** Either member flow switched on: only then is a bearer token looked at. Pure. */
103
+ export const memberAuthorizationEnabled = (settings: DeliveryApiSettings): boolean =>
104
+ settings.memberAuthorization.authorizationCodeFlow.enabled ||
105
+ settings.memberAuthorization.clientCredentialsFlow.enabled
106
+
107
+ /** Whether a content type is served, by Umbraco's allow-then-disallow rule. Pure. */
108
+ export function isAllowedContentType(settings: DeliveryApiSettings, alias: string): boolean {
109
+ const lower = alias.toLowerCase()
110
+ if (settings.allowedContentTypeAliases.length > 0)
111
+ return settings.allowedContentTypeAliases.some((a) => a.toLowerCase() === lower)
112
+ return !settings.disallowedContentTypeAliases.some((a) => a.toLowerCase() === lower)
113
+ }
114
+
115
+ /** The configuration problems that switch a member flow off, said once at boot. Pure. */
116
+ export function memberAuthorizationProblems(settings: DeliveryApiSettings): string[] {
117
+ const problems: string[] = []
118
+ const code = settings.memberAuthorization.authorizationCodeFlow
119
+ const absolute = (url: string) => /^[a-z][a-z0-9+.-]*:\/\//i.test(url)
120
+ if (code.enabled) {
121
+ if (code.loginRedirectUrls.length === 0)
122
+ problems.push('No login redirect URLs are set, so member sign-in from an app is off.')
123
+ else if (!code.loginRedirectUrls.every(absolute))
124
+ problems.push(
125
+ 'Every login redirect URL must be absolute, so member sign-in from an app is off.',
126
+ )
127
+ if (!code.logoutRedirectUrls.every(absolute))
128
+ problems.push(
129
+ 'Every logout redirect URL must be absolute, so member sign-in from an app is off.',
130
+ )
131
+ if (!code.loginPage)
132
+ problems.push(
133
+ 'No member login page is set, so a member who is not signed in cannot sign in from an app.',
134
+ )
135
+ }
136
+ return problems
137
+ }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.