slauthjs 1.0.0 → 2.0.0
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/CHANGELOG.md +72 -0
- package/README.md +46 -9
- package/package.json +31 -7
- package/src/account-element.d.ts +8 -0
- package/src/account-element.js +244 -0
- package/src/client.d.ts +86 -7
- package/src/client.js +99 -6
- package/src/core.js +313 -182
- package/src/element.js +161 -59
- package/src/email/console.js +5 -4
- package/src/email/message.js +50 -10
- package/src/email-code.js +32 -18
- package/src/index.d.ts +221 -6
- package/src/index.js +3 -0
- package/src/oauth/flow.js +81 -0
- package/src/oauth/providers.js +171 -0
- package/src/orgs.js +184 -0
- package/src/password.js +81 -0
- package/src/providers.d.ts +28 -0
- package/src/providers.js +1 -0
- package/src/rate-limit.js +41 -0
- package/src/routes/account.js +130 -0
- package/src/routes/email.js +147 -0
- package/src/routes/oauth.js +106 -0
- package/src/routes/orgs.js +144 -0
- package/src/routes/passkeys.js +136 -0
- package/src/routes/password.js +78 -0
- package/src/routes/two-factor.js +89 -0
- package/src/session.js +98 -16
- package/src/stores/d1.js +14 -0
- package/src/stores/index.d.ts +2 -2
- package/src/stores/kv.js +9 -0
- package/src/stores/memory.js +5 -0
- package/src/stores/sqlite.js +8 -1
- package/src/tokens.js +119 -0
- package/src/two-factor.js +142 -0
- package/src/ui.js +49 -0
- package/src/users.js +92 -22
- package/src/util.js +37 -0
package/src/client.js
CHANGED
|
@@ -18,11 +18,20 @@ export class SlauthError extends Error {
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
export function createClient({ basePath = '/auth', fetch: doFetch = (...args) => fetch(...args) } = {}) {
|
|
21
|
+
export function createClient({ basePath = '/auth', fetch: doFetch = (...args) => fetch(...args), token: bearer } = {}) {
|
|
22
22
|
const base = basePath.replace(/\/+$/, '')
|
|
23
|
+
// Apps that can't keep cookies (React Native, CLIs) pass `token: { get, set }` and get bearer tokens instead.
|
|
24
|
+
const extra = () => {
|
|
25
|
+
if (!bearer) return {}
|
|
26
|
+
const value = bearer.get()
|
|
27
|
+
return { 'X-Slauth-Transport': 'bearer', ...(value ? { Authorization: `Bearer ${value}` } : {}) }
|
|
28
|
+
}
|
|
23
29
|
|
|
24
30
|
async function call(path, body) {
|
|
25
|
-
const init =
|
|
31
|
+
const init =
|
|
32
|
+
body === undefined
|
|
33
|
+
? { credentials: 'same-origin', headers: extra() }
|
|
34
|
+
: { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', ...extra() }, body: JSON.stringify(body) }
|
|
26
35
|
let res
|
|
27
36
|
try {
|
|
28
37
|
res = await doFetch(`${base}/${path}`, init)
|
|
@@ -34,9 +43,21 @@ export function createClient({ basePath = '/auth', fetch: doFetch = (...args) =>
|
|
|
34
43
|
const { error, code, ...rest } = data
|
|
35
44
|
throw new SlauthError(error ?? `HTTP ${res.status}`, code ?? 'server-error', res.status, rest)
|
|
36
45
|
}
|
|
46
|
+
if (bearer && typeof data.token === 'string' && path !== 'token') await bearer.set(data.token)
|
|
47
|
+
if (bearer && path === 'signout') await bearer.set(null)
|
|
48
|
+
return data
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Remembers the pending two-factor sign-in for bearer apps. */
|
|
52
|
+
let pendingToken = null
|
|
53
|
+
const signIn = async (path, body) => {
|
|
54
|
+
const data = await call(path, body)
|
|
55
|
+
if (data.pendingToken) pendingToken = data.pendingToken
|
|
37
56
|
return data
|
|
38
57
|
}
|
|
39
58
|
|
|
59
|
+
let cachedToken = null
|
|
60
|
+
|
|
40
61
|
const passkey = {
|
|
41
62
|
/** True when this browser can use passkeys. */
|
|
42
63
|
supported: () => typeof window !== 'undefined' && typeof window.PublicKeyCredential === 'function',
|
|
@@ -65,18 +86,90 @@ export function createClient({ basePath = '/auth', fetch: doFetch = (...args) =>
|
|
|
65
86
|
},
|
|
66
87
|
|
|
67
88
|
list: () => call('passkeys'),
|
|
89
|
+
rename: (id, name) => call('passkey/rename', { id, name }),
|
|
68
90
|
remove: (id) => call('passkey/delete', { id }),
|
|
69
91
|
}
|
|
70
92
|
|
|
93
|
+
const go = (url) => {
|
|
94
|
+
if (typeof window !== 'undefined') window.location.assign(url)
|
|
95
|
+
return url
|
|
96
|
+
}
|
|
97
|
+
const providerUrl = (id, params) => `${base}/oauth/${encodeURIComponent(id)}?${new URLSearchParams(params)}`
|
|
98
|
+
|
|
71
99
|
return {
|
|
72
|
-
/** `{ user }` (null when signed out),
|
|
100
|
+
/** `{ user }` (null when signed out), `twoFactor: 'pending'` mid sign-in, and `lock` when the app lock is on. */
|
|
73
101
|
session: () => call('session'),
|
|
102
|
+
/** What this app offers: providers, passwords, magic links, two-factor, organizations. */
|
|
103
|
+
config: () => call('config'),
|
|
74
104
|
email: {
|
|
75
|
-
start: (email) => call('email/start', { email }),
|
|
76
|
-
verify: (email, code) =>
|
|
105
|
+
start: (email, options = {}) => call('email/start', { email, ...options }),
|
|
106
|
+
verify: (email, code) => signIn('email/verify', { email, code }),
|
|
107
|
+
/** Finishes a magic link (the page the link opens does this for you). */
|
|
108
|
+
link: (token) => signIn('email/link', { token }),
|
|
109
|
+
change: {
|
|
110
|
+
start: (email) => call('email/change/start', { email }),
|
|
111
|
+
verify: (email, code) => call('email/change/verify', { email, code }),
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
password: {
|
|
115
|
+
signIn: (email, password) => signIn('password/signin', { email, password }),
|
|
116
|
+
/** Emails a code; once it's used (email.verify) the password is saved. */
|
|
117
|
+
signUp: (email, password, options = {}) => call('password/signup', { email, password, ...options }),
|
|
118
|
+
set: (password, current) => call('password/set', { password, current }),
|
|
119
|
+
remove: () => call('password/remove', {}),
|
|
120
|
+
},
|
|
121
|
+
/** "Sign in with …": goes to the provider and comes back to `redirect`. */
|
|
122
|
+
oauth: {
|
|
123
|
+
signIn: (provider, { redirect } = {}) => go(providerUrl(provider, redirect ? { redirect } : {})),
|
|
124
|
+
link: (provider, { redirect } = {}) => go(providerUrl(provider, { link: '1', ...(redirect ? { redirect } : {}) })),
|
|
125
|
+
url: (provider, { redirect } = {}) => providerUrl(provider, redirect ? { redirect } : {}),
|
|
126
|
+
},
|
|
127
|
+
twoFactor: {
|
|
128
|
+
/** The second step of signing in. Pass { recoveryCode } instead of a code if the phone is lost. */
|
|
129
|
+
verify: (code) => call('two-factor/verify', { ...(typeof code === 'string' ? { code } : code), ...(pendingToken ? { pendingToken } : {}) }),
|
|
130
|
+
setup: () => call('two-factor/setup', {}),
|
|
131
|
+
enable: (code) => call('two-factor/enable', { code }),
|
|
132
|
+
disable: (code) => call('two-factor/disable', typeof code === 'string' ? { code } : code),
|
|
133
|
+
recoveryCodes: (code) => call('two-factor/recovery-codes', { code }),
|
|
77
134
|
},
|
|
78
135
|
passkey,
|
|
79
|
-
signOut: () =>
|
|
136
|
+
signOut: async () => {
|
|
137
|
+
cachedToken = null
|
|
138
|
+
return call('signout', {})
|
|
139
|
+
},
|
|
140
|
+
user: {
|
|
141
|
+
update: (patch) => call('user', patch),
|
|
142
|
+
delete: () => call('user/delete', {}),
|
|
143
|
+
},
|
|
144
|
+
sessions: {
|
|
145
|
+
list: () => call('sessions'),
|
|
146
|
+
revoke: (id) => call('sessions/revoke', { id }),
|
|
147
|
+
revokeOthers: () => call('sessions/revoke-others', {}),
|
|
148
|
+
},
|
|
149
|
+
accounts: {
|
|
150
|
+
list: () => call('accounts'),
|
|
151
|
+
unlink: (provider) => call('accounts/unlink', { provider }),
|
|
152
|
+
},
|
|
153
|
+
/** A JWT for your other services, reused until a minute before it expires. */
|
|
154
|
+
async getToken() {
|
|
155
|
+
if (cachedToken && cachedToken.expiresAt - 60_000 > Date.now()) return cachedToken.token
|
|
156
|
+
cachedToken = await call('token')
|
|
157
|
+
return cachedToken.token
|
|
158
|
+
},
|
|
159
|
+
orgs: {
|
|
160
|
+
list: () => call('orgs'),
|
|
161
|
+
create: (name) => call('orgs', { name }),
|
|
162
|
+
get: (id) => call(`orgs/${encodeURIComponent(id)}`),
|
|
163
|
+
rename: (id, name) => call(`orgs/${encodeURIComponent(id)}`, { name }),
|
|
164
|
+
delete: (id) => call(`orgs/${encodeURIComponent(id)}/delete`, {}),
|
|
165
|
+
invite: (id, email, role) => call(`orgs/${encodeURIComponent(id)}/invite`, { email, role }),
|
|
166
|
+
revokeInvite: (id, inviteId) => call(`orgs/${encodeURIComponent(id)}/invites/revoke`, { id: inviteId }),
|
|
167
|
+
setRole: (id, userId, role) => call(`orgs/${encodeURIComponent(id)}/members/role`, { userId, role }),
|
|
168
|
+
removeMember: (id, userId) => call(`orgs/${encodeURIComponent(id)}/members/remove`, { userId }),
|
|
169
|
+
invites: () => call('invites'),
|
|
170
|
+
accept: (inviteId) => call('invites/accept', { id: inviteId }),
|
|
171
|
+
decline: (inviteId) => call('invites/decline', { id: inviteId }),
|
|
172
|
+
},
|
|
80
173
|
lock: {
|
|
81
174
|
unlock: (pin) => call('lock/login', { pin }),
|
|
82
175
|
setup: (pin, policy) => call('lock/setup', { pin, policy }),
|