add-cms
This commit is contained in:
8
source/admin/packages/decap-cms-ui-auth/CHANGELOG.md
Normal file
8
source/admin/packages/decap-cms-ui-auth/CHANGELOG.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## 3.2.3 (2025-07-31)
|
||||
|
||||
**Note:** Version bump only for package decap-cms-ui-auth
|
||||
28
source/admin/packages/decap-cms-ui-auth/README.md
Normal file
28
source/admin/packages/decap-cms-ui-auth/README.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# decap-cms-ui-auth
|
||||
|
||||
Authentication UI pages used by the decap-cms-backend-* packages.
|
||||
|
||||
## Common Behavior
|
||||
|
||||
* An authenticator must return the following fields:
|
||||
* email
|
||||
* token?
|
||||
* expires?
|
||||
|
||||
## Components
|
||||
|
||||
* **NetlifyAuthenticationPage**
|
||||
* Username and password fields that are passed to Netlify Identity.
|
||||
* Requires a static `authClient` value set before login will work, expected to be set by the backend implementation.
|
||||
* Returns object that satisfies the GitGatewayUser type (and inherited Credentials type) from Netlify
|
||||
* **PKCEAuthenticationPage**
|
||||
* OAuth2 PKCE flow with optional OIDC auto-configuration.
|
||||
* Returns object that satisfies the GitGatewayUser type (and inherited Credentials type), with additional data:
|
||||
* token (part of Credentials definition): the access token
|
||||
* idToken: if available
|
||||
* claims: if available (decoded access token)
|
||||
* idClaims: if available (decoded ID token)
|
||||
* email: mapped email value from the token claims, if available
|
||||
* user_metadata.full_name: mapped value from the token claims, if available
|
||||
* user_metadata.avatar_url: mapped value from the token claims, if available
|
||||
|
||||
16
source/admin/packages/decap-cms-ui-auth/index.d.ts
vendored
Normal file
16
source/admin/packages/decap-cms-ui-auth/index.d.ts
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
declare module 'decap-cms-ui-auth' {
|
||||
import React from 'react';
|
||||
|
||||
import type { Implementation } from 'decap-cms-lib-util/src';
|
||||
|
||||
class PKCEAuthenticationPage extends React.Component {
|
||||
constructor({ backend }: { backend: Implementation });
|
||||
handleLogin(e: ChangeEvent<HTMLInputElement>): void;
|
||||
}
|
||||
class NetlifyAuthenticationPage extends React.Component {
|
||||
handleLogin(e: ChangeEvent<HTMLInputElement>): void;
|
||||
static authClient: () => Promise;
|
||||
}
|
||||
|
||||
export { PKCEAuthenticationPage, NetlifyAuthenticationPage };
|
||||
}
|
||||
33
source/admin/packages/decap-cms-ui-auth/package.json
Normal file
33
source/admin/packages/decap-cms-ui-auth/package.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "decap-cms-ui-auth",
|
||||
"description": "UI auth library for Decap CMS",
|
||||
"version": "3.2.3",
|
||||
"repository": "https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-ui-auth",
|
||||
"bugs": "https://github.com/decaporg/decap-cms/issues",
|
||||
"module": "dist/esm/index.js",
|
||||
"main": "dist/decap-cms-ui-auth.js",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"decap-cms",
|
||||
"ui",
|
||||
"auth"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"scripts": {
|
||||
"develop": "npm run build:esm -- --watch",
|
||||
"build": "cross-env NODE_ENV=production webpack",
|
||||
"build:esm": "cross-env NODE_ENV=esm babel src --out-dir dist/esm --ignore \"**/__tests__\" --root-mode upward --extensions \".js,.jsx,.ts,.tsx\""
|
||||
},
|
||||
"dependencies": {
|
||||
"jwt-decode": "^3.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.11.1",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"decap-cms-lib-auth": "^3.0.0",
|
||||
"decap-cms-ui-default": "^3.0.0",
|
||||
"lodash": "^4.17.11",
|
||||
"prop-types": "^15.7.2",
|
||||
"react": "^18.2.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import partial from 'lodash/partial';
|
||||
import {
|
||||
AuthenticationPage,
|
||||
buttons,
|
||||
shadows,
|
||||
colors,
|
||||
colorsRaw,
|
||||
lengths,
|
||||
zIndex,
|
||||
} from 'decap-cms-ui-default';
|
||||
|
||||
const LoginButton = styled.button`
|
||||
${buttons.button};
|
||||
${shadows.dropDeep};
|
||||
${buttons.default};
|
||||
${buttons.gray};
|
||||
|
||||
padding: 0 30px;
|
||||
display: block;
|
||||
margin-top: 20px;
|
||||
margin-left: auto;
|
||||
`;
|
||||
|
||||
const AuthForm = styled.form`
|
||||
width: 350px;
|
||||
margin-top: -80px;
|
||||
`;
|
||||
|
||||
const AuthInput = styled.input`
|
||||
background-color: ${colorsRaw.white};
|
||||
border-radius: ${lengths.borderRadius};
|
||||
|
||||
font-size: 14px;
|
||||
padding: 10px;
|
||||
margin-bottom: 15px;
|
||||
margin-top: 6px;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
z-index: ${zIndex.zIndex1};
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
box-shadow: inset 0 0 0 2px ${colors.active};
|
||||
}
|
||||
`;
|
||||
|
||||
const ErrorMessage = styled.p`
|
||||
color: ${colors.errorText};
|
||||
`;
|
||||
|
||||
let component = null;
|
||||
|
||||
if (window.netlifyIdentity) {
|
||||
window.netlifyIdentity.on('login', user => {
|
||||
component && component.handleIdentityLogin(user);
|
||||
});
|
||||
window.netlifyIdentity.on('logout', () => {
|
||||
component && component.handleIdentityLogout();
|
||||
});
|
||||
window.netlifyIdentity.on('error', err => {
|
||||
component && component.handleIdentityError(err);
|
||||
});
|
||||
}
|
||||
|
||||
export default class NetlifyAuthenticationPage extends React.Component {
|
||||
static authClient;
|
||||
|
||||
static propTypes = {
|
||||
onLogin: PropTypes.func.isRequired,
|
||||
inProgress: PropTypes.bool.isRequired,
|
||||
error: PropTypes.node,
|
||||
config: PropTypes.object.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
component = this;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// Manually validate PropTypes - React 19 breaking change
|
||||
PropTypes.checkPropTypes(
|
||||
NetlifyAuthenticationPage.propTypes,
|
||||
this.props,
|
||||
'prop',
|
||||
'GitGatewayAuthenticationPage',
|
||||
);
|
||||
|
||||
if (!this.loggedIn && window.netlifyIdentity && window.netlifyIdentity.currentUser()) {
|
||||
this.props.onLogin(window.netlifyIdentity.currentUser());
|
||||
window.netlifyIdentity.close();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
component = null;
|
||||
}
|
||||
|
||||
handleIdentityLogin = user => {
|
||||
this.props.onLogin(user);
|
||||
window.netlifyIdentity.close();
|
||||
};
|
||||
|
||||
handleIdentityLogout = () => {
|
||||
window.netlifyIdentity.open();
|
||||
};
|
||||
|
||||
handleIdentityError = err => {
|
||||
if (err?.message?.match(/^Failed to load settings from.+\.netlify\/identity$/)) {
|
||||
window.netlifyIdentity.close();
|
||||
this.setState({
|
||||
errors: { identity: this.props.t('auth.errors.identitySettings') },
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
handleIdentity = () => {
|
||||
const user = window.netlifyIdentity.currentUser();
|
||||
if (user) {
|
||||
this.props.onLogin(user);
|
||||
} else {
|
||||
window.netlifyIdentity.open();
|
||||
}
|
||||
};
|
||||
|
||||
state = { email: '', password: '', errors: {} };
|
||||
|
||||
handleChange = (name, e) => {
|
||||
this.setState({ ...this.state, [name]: e.target.value });
|
||||
};
|
||||
|
||||
handleLogin = async e => {
|
||||
e.preventDefault();
|
||||
|
||||
const { email, password } = this.state;
|
||||
const { t } = this.props;
|
||||
const errors = {};
|
||||
if (!email) {
|
||||
errors.email = t('auth.errors.email');
|
||||
}
|
||||
if (!password) {
|
||||
errors.password = t('auth.errors.password');
|
||||
}
|
||||
|
||||
if (Object.keys(errors).length > 0) {
|
||||
this.setState({ errors });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const client = await NetlifyAuthenticationPage.authClient();
|
||||
const user = await client.login(this.state.email, this.state.password, true);
|
||||
this.props.onLogin(user);
|
||||
} catch (error) {
|
||||
this.setState({
|
||||
errors: { server: error.description || error.msg || error },
|
||||
loggingIn: false,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { errors } = this.state;
|
||||
const { error, inProgress, config, t } = this.props;
|
||||
|
||||
if (window.netlifyIdentity) {
|
||||
if (errors.identity) {
|
||||
return (
|
||||
<AuthenticationPage
|
||||
logoUrl={config.logo_url} // Deprecated, replaced by `logo.src`
|
||||
logo={config.logo}
|
||||
siteUrl={config.site_url}
|
||||
onLogin={this.handleIdentity}
|
||||
renderPageContent={() => (
|
||||
<a
|
||||
href="https://docs.netlify.com/visitor-access/git-gateway/#setup-and-settings"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{errors.identity}
|
||||
</a>
|
||||
)}
|
||||
t={t}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<AuthenticationPage
|
||||
logoUrl={config.logo_url} // Deprecated, replaced by `logo.src`
|
||||
logo={config.logo}
|
||||
siteUrl={config.site_url}
|
||||
onLogin={this.handleIdentity}
|
||||
renderButtonContent={() => t('auth.loginWithNetlifyIdentity')}
|
||||
t={t}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<AuthenticationPage
|
||||
logoUrl={config.logo_url} // Deprecated, replaced by `logo.src`
|
||||
logo={config.logo}
|
||||
siteUrl={config.site_url}
|
||||
renderPageContent={() => (
|
||||
<AuthForm onSubmit={this.handleLogin}>
|
||||
{!error ? null : <ErrorMessage>{error}</ErrorMessage>}
|
||||
{!errors.server ? null : <ErrorMessage>{String(errors.server)}</ErrorMessage>}
|
||||
<ErrorMessage>{errors.email || null}</ErrorMessage>
|
||||
<AuthInput
|
||||
type="text"
|
||||
name="email"
|
||||
placeholder="Email"
|
||||
value={this.state.email}
|
||||
onChange={partial(this.handleChange, 'email')}
|
||||
/>
|
||||
<ErrorMessage>{errors.password || null}</ErrorMessage>
|
||||
<AuthInput
|
||||
type="password"
|
||||
name="password"
|
||||
placeholder="Password"
|
||||
value={this.state.password}
|
||||
onChange={partial(this.handleChange, 'password')}
|
||||
/>
|
||||
<LoginButton disabled={inProgress}>
|
||||
{inProgress ? t('auth.loggingIn') : t('auth.login')}
|
||||
</LoginButton>
|
||||
</AuthForm>
|
||||
)}
|
||||
t={t}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import styled from '@emotion/styled';
|
||||
import jwtDecode from 'jwt-decode';
|
||||
import { PkceAuthenticator } from 'decap-cms-lib-auth';
|
||||
import { AuthenticationPage, Icon } from 'decap-cms-ui-default';
|
||||
|
||||
const LoginButtonIcon = styled(Icon)`
|
||||
margin-right: 18px;
|
||||
`;
|
||||
|
||||
function normalizeClaimsToUser(
|
||||
email_claim,
|
||||
full_name_claim,
|
||||
first_name_claim,
|
||||
last_name_claim,
|
||||
avatar_url_claim,
|
||||
) {
|
||||
return (user, claims) => {
|
||||
if (!claims) return;
|
||||
|
||||
if (!user.email && claims[email_claim]) {
|
||||
user.email = claims[email_claim];
|
||||
}
|
||||
if (!user.user_metadata.full_name && full_name_claim && claims[full_name_claim]) {
|
||||
user.user_metadata.full_name = claims[full_name_claim];
|
||||
}
|
||||
if (!user.user_metadata.full_name && (first_name_claim || last_name_claim)) {
|
||||
const name = [];
|
||||
if (claims[first_name_claim]) name.push(claims[first_name_claim]);
|
||||
if (claims[last_name_claim]) name.push(claims[last_name_claim]);
|
||||
if (name.length) {
|
||||
user.user_metadata.full_name = name.join(' ');
|
||||
}
|
||||
}
|
||||
if (!user.user_metadata.avatar_url && avatar_url_claim && claims[avatar_url_claim]) {
|
||||
user.user_metadata.avatar_url = claims[avatar_url_claim];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default class PKCEAuthenticationPage extends React.Component {
|
||||
static propTypes = {
|
||||
inProgress: PropTypes.bool,
|
||||
config: PropTypes.object.isRequired,
|
||||
onLogin: PropTypes.func.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
state = {};
|
||||
|
||||
componentDidMount() {
|
||||
// Old configuration options, available from the backend configuration
|
||||
const {
|
||||
base_url: backend_base_url = '',
|
||||
app_id: backend_app_id = '',
|
||||
auth_endpoint: backend_auth_endpoint = 'oauth2/authorize',
|
||||
auth_token_endpoint: backend_auth_token_endpoint = 'oauth2/token',
|
||||
} = this.props.config.backend;
|
||||
// New configuration options, separately defined in the "auth" configuration
|
||||
const {
|
||||
use_oidc = false,
|
||||
base_url = backend_base_url,
|
||||
auth_endpoint = backend_auth_endpoint,
|
||||
auth_token_endpoint = backend_auth_token_endpoint,
|
||||
app_id = backend_app_id,
|
||||
auth_token_endpoint_content_type = 'application/x-www-form-urlencoded; charset=utf-8',
|
||||
email_claim = 'email',
|
||||
full_name_claim,
|
||||
first_name_claim,
|
||||
last_name_claim,
|
||||
avatar_url_claim,
|
||||
} = this.props.config.auth || {};
|
||||
|
||||
const normalizeClaims = normalizeClaimsToUser(
|
||||
email_claim,
|
||||
full_name_claim,
|
||||
first_name_claim,
|
||||
last_name_claim,
|
||||
avatar_url_claim,
|
||||
);
|
||||
|
||||
this.auth = new PkceAuthenticator({
|
||||
base_url,
|
||||
app_id,
|
||||
use_oidc,
|
||||
auth_endpoint,
|
||||
auth_token_endpoint,
|
||||
auth_token_endpoint_content_type,
|
||||
});
|
||||
|
||||
// Complete authentication if we were redirected back from the provider.
|
||||
this.auth.completeAuth((err, data) => {
|
||||
if (err) {
|
||||
this.setState({ loginError: err.toString() });
|
||||
return;
|
||||
}
|
||||
|
||||
data.user_metadata = {};
|
||||
if (data.access_token) {
|
||||
data.token = data.access_token;
|
||||
try {
|
||||
data.claims = jwtDecode(data.access_token);
|
||||
normalizeClaims(data, data.claims);
|
||||
} catch {
|
||||
/* Ignore */
|
||||
}
|
||||
}
|
||||
if (data.id_token) {
|
||||
try {
|
||||
data.idClaims = jwtDecode(data.id_token);
|
||||
normalizeClaims(data, data.idClaims);
|
||||
} catch {
|
||||
/* Ignore */
|
||||
}
|
||||
}
|
||||
|
||||
this.props.onLogin(data);
|
||||
});
|
||||
}
|
||||
|
||||
handleLogin = e => {
|
||||
e.preventDefault();
|
||||
const scope = this.props.config.auth?.scope || this.props.config.auth_scope || 'openid email';
|
||||
this.auth.authenticate({ scope }, (err, data) => {
|
||||
if (err) {
|
||||
this.setState({ loginError: err.toString() });
|
||||
return;
|
||||
}
|
||||
this.props.onLogin(data);
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { inProgress, config, t } = this.props;
|
||||
return (
|
||||
<AuthenticationPage
|
||||
onLogin={this.handleLogin}
|
||||
loginDisabled={inProgress}
|
||||
loginErrorMessage={this.state.loginError}
|
||||
logoUrl={config.logo_url} // Deprecated, replaced by `logo.src`
|
||||
logo={config.logo}
|
||||
siteUrl={config.site_url}
|
||||
renderButtonContent={() => (
|
||||
<React.Fragment>
|
||||
<LoginButtonIcon type="link" /> {inProgress ? t('auth.loggingIn') : t('auth.login')}
|
||||
</React.Fragment>
|
||||
)}
|
||||
t={t}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
import NetlifyAuthenticationPage from '../NetlifyAuthenticationPage';
|
||||
|
||||
window.netlifyIdentity = {
|
||||
currentUser: jest.fn(),
|
||||
on: jest.fn(),
|
||||
close: jest.fn(),
|
||||
};
|
||||
|
||||
describe('NetlifyAuthenticationPage', () => {
|
||||
const props = {
|
||||
config: { logo: { src: 'logo_url' } },
|
||||
t: jest.fn(key => key),
|
||||
onLogin: jest.fn(),
|
||||
inProgress: false,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
jest.resetModules();
|
||||
});
|
||||
|
||||
it('should render with identity error', () => {
|
||||
// obtain mock calls
|
||||
require('../NetlifyAuthenticationPage');
|
||||
|
||||
function TestComponent() {
|
||||
const { asFragment } = render(<NetlifyAuthenticationPage {...props} />);
|
||||
|
||||
const errorCallback = window.netlifyIdentity.on.mock.calls.find(
|
||||
call => call[0] === 'error',
|
||||
)[1];
|
||||
|
||||
errorCallback(
|
||||
new Error('Failed to load settings from https://site.netlify.com/.netlify/identity'),
|
||||
);
|
||||
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
}
|
||||
|
||||
TestComponent();
|
||||
});
|
||||
|
||||
test('should render with no identity error', () => {
|
||||
function TestComponent() {
|
||||
const { asFragment } = render(<NetlifyAuthenticationPage {...props} />);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
}
|
||||
|
||||
TestComponent();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,281 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`NetlifyAuthenticationPage should render with identity error 1`] = `
|
||||
<DocumentFragment>
|
||||
.emotion-0 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-flex-flow: column nowrap;
|
||||
-webkit-flex-flow: column nowrap;
|
||||
-ms-flex-flow: column nowrap;
|
||||
flex-flow: column nowrap;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
-webkit-box-pack: center;
|
||||
-ms-flex-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
justify-content: center;
|
||||
gap: 50px;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.emotion-2 {
|
||||
width: 300px;
|
||||
height: auto;
|
||||
text-align: center;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.emotion-4 {
|
||||
border: 0;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 4px 12px 0 rgba(68, 74, 87, 0.15),0 1px 3px 0 rgba(68, 74, 87, 0.25);
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
font-weight: 500;
|
||||
padding: 0 15px;
|
||||
background-color: #798291;
|
||||
color: #fff;
|
||||
background-color: #313d3e;
|
||||
color: #fff;
|
||||
padding: 0 12px;
|
||||
margin-top: 0;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.emotion-4:focus,
|
||||
.emotion-4:hover {
|
||||
color: #fff;
|
||||
background-color: #555a65;
|
||||
}
|
||||
|
||||
.emotion-4[disabled] {
|
||||
background-color: #eff0f4;
|
||||
color: #798291;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.emotion-7 {
|
||||
display: inline-block;
|
||||
line-height: 0;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
-webkit-transform: rotate(0deg);
|
||||
-moz-transform: rotate(0deg);
|
||||
-ms-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
color: #c4c6d2;
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
}
|
||||
|
||||
.emotion-7 path:not(.no-fill),
|
||||
.emotion-7 circle:not(.no-fill),
|
||||
.emotion-7 polygon:not(.no-fill),
|
||||
.emotion-7 rect:not(.no-fill) {
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.emotion-7 path.clipped {
|
||||
fill: transparent;
|
||||
}
|
||||
|
||||
.emotion-7 svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
<section
|
||||
class="emotion-0 emotion-1"
|
||||
>
|
||||
<span
|
||||
class="emotion-2 emotion-3"
|
||||
>
|
||||
<img
|
||||
alt="Logo"
|
||||
src="logo_url"
|
||||
/>
|
||||
</span>
|
||||
<button
|
||||
class="emotion-4 emotion-5"
|
||||
>
|
||||
auth.loginWithNetlifyIdentity
|
||||
</button>
|
||||
<span
|
||||
class="emotion-6 emotion-7 emotion-8"
|
||||
>
|
||||
<svg
|
||||
fill="none"
|
||||
height="90"
|
||||
viewBox="0 0 335 90"
|
||||
width="335"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
class="no-fill"
|
||||
d="M30.73.15 0 2.95l3.67 40.21 20.03-1.83-1.99-21.86 10.71-.98c10.61-.97 19.14 7.53 20.29 20.19l19.67-1.79C70.34 14.72 51.64-1.75 30.73.15ZM73.61 49.51c0 12.72-7.73 21.95-18.37 21.95H44.49V49.47H24.38v40.42h30.86c21.02 0 38.12-18.11 38.12-40.38v-.04H73.61v.04Z"
|
||||
fill="#FF0082"
|
||||
/>
|
||||
<path
|
||||
class="no-fill"
|
||||
d="M131.65 23.71h20.01c14.41 0 24.29 9.09 24.29 23.06 0 13.97-9.88 23.06-24.29 23.06h-20.01V23.71Zm19.51 37.35c8.75 0 14.47-5.47 14.47-14.29s-5.73-14.29-14.47-14.29h-9.31v28.59h9.31v-.01ZM207.61 58.69l5.22 5.93c-3.15 3.75-7.87 5.73-13.97 5.73-11.7 0-19.32-7.71-19.32-18.25s7.68-18.25 18.12-18.25c9.56 0 17.43 6.59 17.49 17.92l-25.04 5.07c1.45 3.49 4.59 5.27 9 5.27 3.59 0 6.17-1.12 8.5-3.43v.01Zm-18.44-7.64 16.49-3.36c-.94-3.62-3.9-6.06-7.99-6.06-4.91 0-8.31 3.43-8.5 9.42ZM218.25 52.1c0-10.67 7.87-18.25 18.88-18.25 7.11 0 12.71 3.23 15.17 9.02l-7.61 4.28c-1.83-3.36-4.53-4.87-7.61-4.87-4.97 0-8.87 3.62-8.87 9.81s3.9 9.81 8.87 9.81c3.08 0 5.79-1.45 7.61-4.87l7.61 4.35c-2.45 5.67-8.05 8.96-15.17 8.96-11.01 0-18.88-7.58-18.88-18.25v.01ZM290.93 34.38v35.44h-9.38v-4.08c-2.45 3.1-6.04 4.61-10.57 4.61-9.57 0-16.93-7.11-16.93-18.25s7.36-18.25 16.93-18.25c4.15 0 7.68 1.38 10.13 4.28v-3.75h9.82ZM281.3 52.1c0-6.13-3.78-9.81-8.62-9.81S264 45.98 264 52.1c0 6.12 3.78 9.81 8.68 9.81s8.62-3.69 8.62-9.81ZM334.54 52.1c0 11.13-7.36 18.25-16.86 18.25-4.22 0-7.68-1.38-10.19-4.28V82.6h-9.82V34.38h9.38v4.08c2.45-3.1 6.1-4.61 10.63-4.61 9.5 0 16.86 7.11 16.86 18.25Zm-9.94 0c0-6.13-3.71-9.81-8.62-9.81-4.91 0-8.62 3.69-8.62 9.81 0 6.12 3.71 9.81 8.62 9.81 4.91 0 8.62-3.69 8.62-9.81Z"
|
||||
fill="#000"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</section>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`NetlifyAuthenticationPage should render with no identity error 1`] = `
|
||||
<DocumentFragment>
|
||||
.emotion-0 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-flex-flow: column nowrap;
|
||||
-webkit-flex-flow: column nowrap;
|
||||
-ms-flex-flow: column nowrap;
|
||||
flex-flow: column nowrap;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
-webkit-box-pack: center;
|
||||
-ms-flex-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
justify-content: center;
|
||||
gap: 50px;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.emotion-2 {
|
||||
width: 300px;
|
||||
height: auto;
|
||||
text-align: center;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.emotion-4 {
|
||||
border: 0;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 4px 12px 0 rgba(68, 74, 87, 0.15),0 1px 3px 0 rgba(68, 74, 87, 0.25);
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
font-weight: 500;
|
||||
padding: 0 15px;
|
||||
background-color: #798291;
|
||||
color: #fff;
|
||||
background-color: #313d3e;
|
||||
color: #fff;
|
||||
padding: 0 12px;
|
||||
margin-top: 0;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.emotion-4:focus,
|
||||
.emotion-4:hover {
|
||||
color: #fff;
|
||||
background-color: #555a65;
|
||||
}
|
||||
|
||||
.emotion-4[disabled] {
|
||||
background-color: #eff0f4;
|
||||
color: #798291;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.emotion-7 {
|
||||
display: inline-block;
|
||||
line-height: 0;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
-webkit-transform: rotate(0deg);
|
||||
-moz-transform: rotate(0deg);
|
||||
-ms-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
color: #c4c6d2;
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
}
|
||||
|
||||
.emotion-7 path:not(.no-fill),
|
||||
.emotion-7 circle:not(.no-fill),
|
||||
.emotion-7 polygon:not(.no-fill),
|
||||
.emotion-7 rect:not(.no-fill) {
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.emotion-7 path.clipped {
|
||||
fill: transparent;
|
||||
}
|
||||
|
||||
.emotion-7 svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
<section
|
||||
class="emotion-0 emotion-1"
|
||||
>
|
||||
<span
|
||||
class="emotion-2 emotion-3"
|
||||
>
|
||||
<img
|
||||
alt="Logo"
|
||||
src="logo_url"
|
||||
/>
|
||||
</span>
|
||||
<button
|
||||
class="emotion-4 emotion-5"
|
||||
>
|
||||
auth.loginWithNetlifyIdentity
|
||||
</button>
|
||||
<span
|
||||
class="emotion-6 emotion-7 emotion-8"
|
||||
>
|
||||
<svg
|
||||
fill="none"
|
||||
height="90"
|
||||
viewBox="0 0 335 90"
|
||||
width="335"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
class="no-fill"
|
||||
d="M30.73.15 0 2.95l3.67 40.21 20.03-1.83-1.99-21.86 10.71-.98c10.61-.97 19.14 7.53 20.29 20.19l19.67-1.79C70.34 14.72 51.64-1.75 30.73.15ZM73.61 49.51c0 12.72-7.73 21.95-18.37 21.95H44.49V49.47H24.38v40.42h30.86c21.02 0 38.12-18.11 38.12-40.38v-.04H73.61v.04Z"
|
||||
fill="#FF0082"
|
||||
/>
|
||||
<path
|
||||
class="no-fill"
|
||||
d="M131.65 23.71h20.01c14.41 0 24.29 9.09 24.29 23.06 0 13.97-9.88 23.06-24.29 23.06h-20.01V23.71Zm19.51 37.35c8.75 0 14.47-5.47 14.47-14.29s-5.73-14.29-14.47-14.29h-9.31v28.59h9.31v-.01ZM207.61 58.69l5.22 5.93c-3.15 3.75-7.87 5.73-13.97 5.73-11.7 0-19.32-7.71-19.32-18.25s7.68-18.25 18.12-18.25c9.56 0 17.43 6.59 17.49 17.92l-25.04 5.07c1.45 3.49 4.59 5.27 9 5.27 3.59 0 6.17-1.12 8.5-3.43v.01Zm-18.44-7.64 16.49-3.36c-.94-3.62-3.9-6.06-7.99-6.06-4.91 0-8.31 3.43-8.5 9.42ZM218.25 52.1c0-10.67 7.87-18.25 18.88-18.25 7.11 0 12.71 3.23 15.17 9.02l-7.61 4.28c-1.83-3.36-4.53-4.87-7.61-4.87-4.97 0-8.87 3.62-8.87 9.81s3.9 9.81 8.87 9.81c3.08 0 5.79-1.45 7.61-4.87l7.61 4.35c-2.45 5.67-8.05 8.96-15.17 8.96-11.01 0-18.88-7.58-18.88-18.25v.01ZM290.93 34.38v35.44h-9.38v-4.08c-2.45 3.1-6.04 4.61-10.57 4.61-9.57 0-16.93-7.11-16.93-18.25s7.36-18.25 16.93-18.25c4.15 0 7.68 1.38 10.13 4.28v-3.75h9.82ZM281.3 52.1c0-6.13-3.78-9.81-8.62-9.81S264 45.98 264 52.1c0 6.12 3.78 9.81 8.68 9.81s8.62-3.69 8.62-9.81ZM334.54 52.1c0 11.13-7.36 18.25-16.86 18.25-4.22 0-7.68-1.38-10.19-4.28V82.6h-9.82V34.38h9.38v4.08c2.45-3.1 6.1-4.61 10.63-4.61 9.5 0 16.86 7.11 16.86 18.25Zm-9.94 0c0-6.13-3.71-9.81-8.62-9.81-4.91 0-8.62 3.69-8.62 9.81 0 6.12 3.71 9.81 8.62 9.81 4.91 0 8.62-3.69 8.62-9.81Z"
|
||||
fill="#000"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</section>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
8
source/admin/packages/decap-cms-ui-auth/src/index.js
Normal file
8
source/admin/packages/decap-cms-ui-auth/src/index.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import NetlifyAuthenticationPage from './NetlifyAuthenticationPage';
|
||||
import PKCEAuthenticationPage from './PKCEAuthenticationPage';
|
||||
|
||||
export const DecapCmsUiAuth = {
|
||||
NetlifyAuthenticationPage,
|
||||
PKCEAuthenticationPage,
|
||||
};
|
||||
export { NetlifyAuthenticationPage, PKCEAuthenticationPage };
|
||||
@@ -0,0 +1,3 @@
|
||||
const { getConfig } = require('../../scripts/webpack.js');
|
||||
|
||||
module.exports = getConfig();
|
||||
Reference in New Issue
Block a user