2
0
mirror of https://github.com/lowdefy/lowdefy.git synced 2025-03-31 15:20:32 +08:00

feat: Next auth implementation work in progress.

This commit is contained in:
Sam 2022-04-29 14:41:02 +02:00
parent 9eb34c8700
commit bf5692aed2
No known key found for this signature in database
GPG Key ID: D004126FCD1A6DF0
10 changed files with 200 additions and 10 deletions
packages
build/src
engine/src/actions
plugins
actions/actions-core/src/actions
plugins/plugin-next-auth/src/auth
server/lib/utils/auth

@ -33,6 +33,9 @@ async function validateAuthConfig({ components }) {
if (type.isNone(components.auth.pages.roles)) {
components.auth.pages.roles = {};
}
if (type.isNone(components.auth.providers)) {
components.auth.providers = [];
}
const { valid } = validate({
schema: lowdefySchema.definitions.authConfig,

@ -216,6 +216,9 @@ export default {
},
},
},
theme: {
type: 'object',
},
},
},
block: {

@ -15,8 +15,8 @@
*/
function createLogout({ context }) {
return function logout() {
return context._internal.lowdefy._internal.auth.logout();
return function logout(params) {
return context._internal.lowdefy._internal.auth.logout(params);
};
}

@ -23,6 +23,6 @@ beforeEach(() => {
});
test('Login action invocation', async () => {
Login({ methods: { login: mockActionMethod }, params: 'call' });
expect(mockActionMethod.mock.calls).toEqual([['call']]);
Login({ methods: { login: mockActionMethod }, params: 'params' });
expect(mockActionMethod.mock.calls).toEqual([['params']]);
});

@ -14,8 +14,8 @@
limitations under the License.
*/
function Logout({ methods: { logout } }) {
return logout();
function Logout({ methods: { logout }, params }) {
return logout(params);
}
export default Logout;

@ -23,6 +23,6 @@ beforeEach(() => {
});
test('Logout action invocation', async () => {
Logout({ methods: { logout: mockActionMethod } });
expect(mockActionMethod.mock.calls).toEqual([[]]);
Logout({ methods: { logout: mockActionMethod }, params: 'params' });
expect(mockActionMethod.mock.calls).toEqual([['params']]);
});

@ -14,5 +14,13 @@
limitations under the License.
*/
export { default as Auth0Provider } from 'next-auth/providers/auth0';
export { default as GoogleProvider } from 'next-auth/providers/google';
// This syntax does not work because next-auth is not an es module.
// export { default as Auth0Provider } from 'next-auth/providers/auth0';
import _auth0 from 'next-auth/providers/auth0';
import _google from 'next-auth/providers/google';
const Auth0Provider = _auth0.default;
const GoogleProvider = _google.default;
export { Auth0Provider, GoogleProvider };

@ -0,0 +1,51 @@
/*
Copyright 2020-2022 Lowdefy, Inc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { type, urlQuery as urlQueryFn } from '@lowdefy/helpers';
import { signIn, signOut } from 'next-auth/react';
function getCallbackUrl({ lowdefy, callbackUrl = {} }) {
const { home, pageId, urlQuery } = callbackUrl;
if ([!home, !pageId].filter((v) => !v).length > 1) {
throw Error(`Invalid Link: To avoid ambiguity, only one of 'home' or 'pageId' can be defined.`);
}
const query = type.isNone(urlQuery) ? '' : `${urlQueryFn.stringify(urlQuery)}`;
if (home === true) {
return `/${lowdefy.home.configured ? '' : lowdefy.home.pageId}${query ? `?${query}` : ''}`;
}
if (type.isString(pageId)) {
return `/${pageId}${query ? `?${query}` : ''}`;
}
return undefined;
}
function getAuthMethods({ lowdefy }) {
function login({ providerId, callbackUrl, authUrl = {} }) {
signIn(providerId, { callbackUrl: getCallbackUrl({ lowdefy, callbackUrl }) }, authUrl.urlQuery);
}
function logout({ callbackUrl }) {
signOut({ callbackUrl: getCallbackUrl({ lowdefy, callbackUrl }) });
}
return {
login,
logout,
};
}
export default getAuthMethods;

@ -0,0 +1,92 @@
/*
Copyright 2020-2022 Lowdefy, Inc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { NodeParser } from '@lowdefy/operators';
import { getSecretsFromEnv } from '@lowdefy/node-utils';
import { _secret } from '@lowdefy/operators-js/operators/server';
import authJson from '../../../build/auth.json';
import getProviders from './getProviders.js';
const nextAuthConfig = {};
let initialized = false;
const callbacks = {
jwt: async ({ token, user, account, profile, isNewUser }) => {
console.log('jwt callback');
console.log('token', token);
console.log('user', user);
console.log('account', account);
console.log('profile', profile);
console.log('isNewUser', isNewUser);
return token;
},
session: async ({ session, user, token }) => {
console.log('session callback');
console.log('session', session);
console.log('user', user);
console.log('token', token);
session.sub = token.sub;
return session;
},
async redirect({ url, baseUrl }) {
console.log('redirect callback', url, baseUrl);
// Allows relative callback URLs
if (url.startsWith('/')) return `${baseUrl}${url}`;
// Allows callback URLs on the same origin
else if (new URL(url).origin === baseUrl) return url;
return baseUrl;
},
};
function getNextAuthConfig() {
if (initialized) return nextAuthConfig;
const operatorsParser = new NodeParser({
// TODO: do we want to support other operators here?
operators: { _secret },
payload: {},
secrets: getSecretsFromEnv(),
user: {},
});
const { output: authConfig, errors: operatorErrors } = operatorsParser.parse({
input: authJson,
location: 'auth',
});
if (operatorErrors.length > 0) {
throw new Error(operatorErrors[0]);
}
nextAuthConfig.providers = getProviders(authConfig);
// We can either configure this using auth config,
// then the user configures this using the _secret operator
// -> authConfig.secret = evaluatedAuthConfig.secret;
// or we can create a fixed env var/secret name that we read.
// -> authConfig.secret = secrets.LOWDEFY_AUTH_SECRET;
nextAuthConfig.secret = 'TODO: Configure secret';
nextAuthConfig.callbacks = callbacks;
nextAuthConfig.theme = authConfig.theme;
initialized = true;
console.log(JSON.stringify(nextAuthConfig, null, 2));
return nextAuthConfig;
}
export default getNextAuthConfig;

@ -0,0 +1,33 @@
/*
Copyright 2020-2022 Lowdefy, Inc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import providers from '../../../build/plugins/auth/providers.js';
// TODO: docs:
// Callback url to configure with provider will be: {{ protocol }}{{ host }}/api/auth/callback/{{ providerId }}
// This depends on providerId, which might cause some issues if users copy an example and change the id.
// We need to allow users to configure ids, since they might have more than one of the same type.
function getProviders(authConfig) {
return authConfig.providers.map((provider) =>
providers[provider.type]({
...provider.properties,
id: provider.id,
})
);
}
export default getProviders;