/
auth-api-methods.js
160 lines (128 loc) · 6.88 KB
/
auth-api-methods.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
// ======================================================
// Netlify Identity Custom API
// ======================================================
// Methods to interact directly with a Netlify Identity (GoTrue) instance.
// These replicate the functionality of the GoTrue-JS library methods
// but are able to run server side / within serverless functions (e.g. do not rely on `window`)
// ------------------------------------------------------
// Netlify Identity methods
// ------------------------------------------------------
export async function signupUser(email, password) {
if (!email) return errorResponse({ error: 'An email is required.' });
if (!password) return errorResponse({ error: 'A password is required.' });
return await ask({ method: 'POST', endpoint: 'signup', data: { email, password } })
}
export async function confirmUser(confirmationToken) {
if (!confirmationToken) return errorResponse({ error: 'A token is required.' });
return await ask({ method: 'POST', endpoint: 'verify', data: { token: confirmationToken, type: 'signup' } })
}
export async function loginUser(email, password) {
if (!email) return errorResponse({ error: 'An email is required.' });
if (!password) return errorResponse({ error: 'A password is required.' });
const data = `grant_type=password&username=${encodeURIComponent(email)}&password=${encodeURIComponent(password)}`;
return await ask({ method: 'POST', endpoint: 'token', data, contentType: 'urlencoded', stringify: false })
}
export async function requestPasswordRecovery(email) {
if (!email) return errorResponse({ error: 'An email is required.' });
return await ask({ method: 'POST', endpoint: 'recover', data: { email } })
}
export async function verifyPasswordRecovery(recoveryToken) {
if (!recoveryToken) return errorResponse({ error: 'A token is required.' });
return await ask({ method: 'POST', endpoint: 'verify', data: { token: recoveryToken, type: 'recovery' } })
}
export async function requestNewAccessToken(refreshToken) {
if (!refreshToken) return errorResponse({ error: 'A token is required.' });
const data = `grant_type=refresh_token&refresh_token=${encodeURIComponent(refreshToken)}`;
return await ask({ method: 'POST', endpoint: 'token', data, contentType: 'urlencoded', stringify: false })
}
export async function getUser(accessToken) {
if (!accessToken) return errorResponse({ error: 'A token is required.' });
return await ask({ method: 'GET', endpoint: 'user', data: null, token: accessToken })
}
export async function updateUser(accessToken, data) {
// used for update password, update email address, etc.
if (!accessToken) return errorResponse({ error: 'A token is required.' });
return await ask({ method: 'PUT', endpoint: 'user', data, token: accessToken })
}
export async function confirmEmailChange(accessToken, emailChangeToken) {
if (!accessToken || !emailChangeToken) return errorResponse({ error: 'A token is required.' });
return await ask({ method: 'PUT', endpoint: 'user', data: { email_change_token: emailChangeToken }, token: accessToken })
}
export async function logoutUser(accessToken) {
if (!accessToken) return errorResponse({ error: 'A token is required.' });
return await ask({ method: 'POST', endpoint: 'logout', data: null, token: accessToken })
}
export async function deleteUser(accessToken) {
// Note - this method requires an admin token, available from a serverless function's 'context.clientContext' object.
// For this, it hits a custom serverless function as a pass thru, instead of the Netlify Identity endpoint directly.
// In this project, the source for this serverless function is at `src/additional_functions/delete-user.js`.
if (!accessToken) return errorResponse({ error: 'A token is required.' });
return await ask({ method: 'GET', endpoint: 'delete-identity', token: accessToken, adminFunction: true })
}
export async function verifyCurrentToken(token) {
if (!token) return errorResponse({ error: 'JWT token not present.' });
const tokenCheck = await getUser(token);
// a valid token will return a user object with email address; an invalid token will return 401
if (tokenCheck.status === 401 || !tokenCheck.email) return { ok: false, statusMessage: 'error', error: tokenCheck.statusText };
return { ok: true, statusMessage: 'success', ...tokenCheck };
}
// ------------------------------------------------------
// Error response utility
// ------------------------------------------------------
function errorResponse(statusCode = 200, body = {}) {
let errorMessage = 'Request error.';
if (statusCode === 404) { errorMessage = 'Item not found.' };
if (statusCode === 500) { errorMessage = 'Application error.' };
body = { statusMessage: 'error', error: errorMessage, ...body }
return {
status: statusCode,
body: JSON.stringify(body),
}
}
// ------------------------------------------------------
// Fetch utility
// ------------------------------------------------------
// Helper method that returns JSON (or text fallback)
const ask = async ({ method, endpoint, data, token, contentType = 'json', stringify = true, includeCredentials = false, adminFunction = false }) => {
const opts = { method, headers: {} };
const baseUrl = !adminFunction ? `${process.env['URL']}/.netlify/identity/` : `${process.env['URL']}/.netlify/functions/`;
if (data && stringify)
opts.body = JSON.stringify(data);
if (data && !stringify)
opts.body = data;
if (data && contentType === 'json')
opts.headers['Content-Type'] = 'application/json';
if (data && contentType === 'urlencoded')
opts.headers['Content-Type'] = 'application/x-www-form-urlencoded';
if (token)
opts.headers['Authorization'] = `Bearer ${token}`;
if (includeCredentials)
opts.credentials = 'include';
try {
const response = await fetch(`${baseUrl}${endpoint}`, opts);
const text = await response.text();
if (!response.ok || (!response.status !== 204 && !text)) {
let errorMessage = response.statusText;
// if a custom error message is present in response, use it
try {
if(JSON.parse(text).error) errorMessage = JSON.parse(text).error;
if(JSON.parse(text).error_description) errorMessage = JSON.parse(text).error_description;
if(JSON.parse(text).msg) errorMessage = JSON.parse(text).msg;
} catch (error) {
if(response.statusText === 'Method Not Allowed') errorMessage = 'Unable to process request.';
}
const errorResponse = { status: response.status, message: errorMessage };
throw errorResponse;
}
try {
// try to parse response into JSON object
return { statusMessage: 'success', ...JSON.parse(text) };
} catch (err) {
// if unsuccessful, use text fallback
return { statusMessage: 'success', ...text };
}
} catch (error) {
const errorMessage = error.message || 'Unable to process request.';
return { statusMessage: `error (${error.status})`, error: errorMessage };
}
};