0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-02-03 21:48:55 -05:00

feat(core,console,schemas,phrases): add privacy policy url (#3315)

This commit is contained in:
simeng-li 2023-03-08 10:56:26 +08:00 committed by GitHub
parent 04cc1fe69a
commit 05ada94f95
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 107 additions and 4 deletions

View file

@ -0,0 +1,10 @@
---
"@logto/console": minor
"@logto/core": minor
"@logto/phrases": minor
"@logto/schemas": minor
---
### Add privacy policy url
In addition to the terms of service url, we also provide a privacy policy url field in the sign-in-experience settings. To better support the end-users' privacy declaration needs.

View file

@ -32,6 +32,19 @@ const TermsForm = () => {
placeholder={t('sign_in_exp.others.terms_of_use.terms_of_use_placeholder')}
/>
</FormField>
<FormField
title="sign_in_exp.others.terms_of_use.privacy_policy"
tip={t('sign_in_exp.others.terms_of_use.privacy_policy_tip')}
>
<TextInput
{...register('privacyPolicyUrl', {
validate: (value) => !value || uriValidator(value) || t('errors.invalid_uri_format'),
})}
hasError={Boolean(errors.termsOfUseUrl)}
errorMessage={errors.termsOfUseUrl?.message}
placeholder={t('sign_in_exp.others.terms_of_use.privacy_policy_placeholder')}
/>
</FormField>
</Card>
);
};

View file

@ -114,5 +114,11 @@ export const getSignUpAndSignInErrorCount = (
return signUpErrorCount + signInMethodErrorCount;
};
export const getOthersErrorCount = (errors: FieldErrorsImpl<DeepRequired<SignInExperienceForm>>) =>
errors.termsOfUseUrl ? 1 : 0;
export const getOthersErrorCount = (
errors: FieldErrorsImpl<DeepRequired<SignInExperienceForm>>
) => {
const termsOfUseUrlErrorCount = errors.termsOfUseUrl ? 1 : 0;
const privacyPolicyUrlErrorCount = errors.privacyPolicyUrl ? 1 : 0;
return termsOfUseUrlErrorCount + privacyPolicyUrlErrorCount;
};

View file

@ -21,6 +21,7 @@ export const mockBranding: Branding = {
};
export const mockTermsOfUseUrl = 'http://silverhand.com/terms';
export const mockPrivacyPolicyUrl = 'http://silverhand.com/privacy';
export const mockLanguageInfo: LanguageInfo = {
autoDetect: true,
@ -58,6 +59,7 @@ export const mockSignInExperience: SignInExperience = {
slogan: 'logto',
},
termsOfUseUrl: mockTermsOfUseUrl,
privacyPolicyUrl: mockPrivacyPolicyUrl,
languageInfo: {
autoDetect: true,
fallbackLanguage: 'en',

View file

@ -35,7 +35,7 @@ describe('sign-in-experience query', () => {
it('findDefaultSignInExperience', async () => {
/* eslint-disable sql/no-unsafe-query */
const expectSql = `
select "tenant_id", "id", "color", "branding", "language_info", "terms_of_use_url", "sign_in", "sign_up", "social_sign_in_connector_targets", "sign_in_mode", "custom_css"
select "tenant_id", "id", "color", "branding", "language_info", "terms_of_use_url", "privacy_policy_url", "sign_in", "sign_up", "social_sign_in_connector_targets", "sign_in_mode", "custom_css"
from "sign_in_experiences"
where "id"=$1
`;

View file

@ -68,6 +68,25 @@ describe('terms of use url', () => {
});
});
describe('privacy policy url', () => {
describe('privacyPolicyUrl', () => {
test.each([undefined, null, '', 'http://silverhand.com/privacy', 'https://logto.dev/privacy'])(
'%p should success',
async (privacyPolicyUrl) => {
const signInExperience = {
privacyPolicyUrl,
};
await expectPatchResponseStatus(signInExperience, 200);
}
);
test.each([' \t\n\r', 'non-url'])('%p should fail', async (privacyPolicyUrl) => {
const signInExperience = { privacyPolicyUrl };
await expectPatchResponseStatus(signInExperience, 400);
});
});
});
describe('languageInfo', () => {
describe('autoDetect', () => {
test.each(validBooleans)('%p should success', async (autoDetect) => {

View file

@ -14,6 +14,7 @@ import {
mockLanguageInfo,
mockAliyunSmsConnector,
mockTermsOfUseUrl,
mockPrivacyPolicyUrl,
} from '#src/__mocks__/index.js';
import { MockTenant } from '#src/test-utils/tenant.js';
import { createRequester } from '#src/utils/test-utils.js';
@ -118,6 +119,7 @@ describe('PATCH /sign-in-exp', () => {
branding: mockBranding,
languageInfo: mockLanguageInfo,
termsOfUseUrl: mockTermsOfUseUrl,
privacyPolicyUrl: mockPrivacyPolicyUrl,
socialSignInConnectorTargets,
signUp: mockSignUp,
signIn: mockSignIn,
@ -135,6 +137,7 @@ describe('PATCH /sign-in-exp', () => {
color: mockColor,
branding: mockBranding,
termsOfUseUrl: mockTermsOfUseUrl,
privacyPolicyUrl: mockPrivacyPolicyUrl,
socialSignInConnectorTargets,
signIn: mockSignIn,
},

View file

@ -33,10 +33,11 @@ export default function signInExperiencesRoutes<T extends AuthedRouter>(
'/sign-in-exp',
koaGuard({
body: SignInExperiences.createGuard
.omit({ id: true, termsOfUseUrl: true })
.omit({ id: true, termsOfUseUrl: true, privacyPolicyUrl: true })
.merge(
object({
termsOfUseUrl: string().url().optional().nullable().or(literal('')),
privacyPolicyUrl: string().url().optional().nullable().or(literal('')),
})
)
.partial(),

View file

@ -23,6 +23,7 @@ describe('admin console sign-in experience', () => {
darkLogoUrl: 'https://logto.io/new-dark-logo.png',
},
termsOfUseUrl: 'https://logto.io/terms',
privacyPolicyUrl: 'https://logto.io/privacy',
};
const updatedSignInExperience = await updateSignInExperience(newSignInExperience);

View file

@ -102,6 +102,9 @@ const sign_in_exp = {
terms_of_use: 'Nutzungsbedingungen',
terms_of_use_placeholder: 'https://beispiel.de/nutzungsbedingungen',
terms_of_use_tip: 'URL zu den Nutzungsbedingungen',
privacy_policy: 'Datenschutzrichtlinien',
privacy_policy_placeholder: 'https://beispiel.de/datenschutzrichtlinien',
privacy_policy_tip: 'URL zu den Datenschutzrichtlinien',
},
languages: {
title: 'SPRACHEN',

View file

@ -100,6 +100,9 @@ const sign_in_exp = {
terms_of_use: 'Terms of use',
terms_of_use_placeholder: 'https://your.terms.of.use/',
terms_of_use_tip: 'Terms of use URL',
privacy_policy: 'Privacy policy',
privacy_policy_placeholder: 'https://your.privacy.policy/',
privacy_policy_tip: 'Privacy policy URL',
},
languages: {
title: 'LANGUAGES',

View file

@ -102,6 +102,9 @@ const sign_in_exp = {
terms_of_use: "Conditions d'utilisation",
terms_of_use_placeholder: 'https://vos.conditions.utilisation/',
terms_of_use_tip: "Conditions d'utilisation URL",
privacy_policy: 'Politique de confidentialité',
privacy_policy_placeholder: 'https://votre.politique.confidentialite/',
privacy_policy_tip: 'Politique de confidentialité URL',
},
languages: {
title: 'LANGAGES',

View file

@ -96,6 +96,9 @@ const sign_in_exp = {
terms_of_use: '이용 약관',
terms_of_use_placeholder: 'https://your.terms.of.use/',
terms_of_use_tip: '이용 약관 URL',
privacy_policy: '개인정보 처리방침',
privacy_policy_placeholder: 'https://your.privacy.policy/',
privacy_policy_tip: '개인정보 처리방침 URL',
},
languages: {
title: '언어',

View file

@ -102,6 +102,9 @@ const sign_in_exp = {
terms_of_use: 'Termos de uso',
terms_of_use_placeholder: 'https://your.terms.of.use/',
terms_of_use_tip: 'URL dos termos de uso',
privacy_policy: 'Política de privacidade',
privacy_policy_placeholder: 'https://your.privacy.policy/',
privacy_policy_tip: 'URL da política de privacidade',
},
languages: {
title: 'IDIOMAS',

View file

@ -100,6 +100,9 @@ const sign_in_exp = {
terms_of_use: 'Termos de uso',
terms_of_use_placeholder: 'https://your.terms.of.use/',
terms_of_use_tip: 'URL dos termos de uso',
privacy_policy: 'Política de privacidade',
privacy_policy_placeholder: 'https://your.privacy.policy/',
privacy_policy_tip: 'URL da política de privacidade',
},
languages: {
title: 'LÍNGUAS',

View file

@ -101,6 +101,9 @@ const sign_in_exp = {
terms_of_use: 'Kullanım koşulları',
terms_of_use_placeholder: 'https://your.terms.of.use/',
terms_of_use_tip: 'Kullanım koşulları URLi',
privacy_policy: 'Gizlilik politikası',
privacy_policy_placeholder: 'https://your.privacy.policy/',
privacy_policy_tip: 'Gizlilik politikası URLi',
},
languages: {
title: 'DİLLER',

View file

@ -94,6 +94,9 @@ const sign_in_exp = {
terms_of_use: '使用条款',
terms_of_use_placeholder: 'https://your.terms.of.use/',
terms_of_use_tip: '使用条款 URL',
privacy_policy: '隐私政策',
privacy_policy_placeholder: 'https://your.privacy.policy/',
privacy_policy_tip: '隐私政策 URL',
},
languages: {
title: '语言',

View file

@ -0,0 +1,20 @@
import { sql } from 'slonik';
import type { AlterationScript } from '../lib/types/alteration.js';
const alteration: AlterationScript = {
up: async (pool) => {
await pool.query(sql`
alter table sign_in_experiences
add column if not exists privacy_policy_url varchar(2048);
`);
},
down: async (pool) => {
await pool.query(sql`
alter table sign_in_experiences
drop column privacy_policy_url;
`);
},
};
export default alteration;

View file

@ -26,6 +26,7 @@ export const createDefaultSignInExperience = (forTenantId: string): Readonly<Sig
fallbackLanguage: 'en' as const,
},
termsOfUseUrl: null,
privacyPolicyUrl: null,
signUp: {
identifiers: [SignInIdentifier.Username],
password: true,

View file

@ -8,6 +8,7 @@ create table sign_in_experiences (
branding jsonb /* @use Branding */ not null,
language_info jsonb /* @use LanguageInfo */ not null,
terms_of_use_url varchar(2048),
privacy_policy_url varchar(2048),
sign_in jsonb /* @use SignIn */ not null,
sign_up jsonb /* @use SignUp */ not null,
social_sign_in_connector_targets jsonb /* @use ConnectorTargets */ not null default '[]'::jsonb,

View file

@ -195,6 +195,7 @@ export const mockSignInExperience: SignInExperience = {
slogan: 'logto',
},
termsOfUseUrl: 'http://terms.of.use/',
privacyPolicyUrl: 'http://privacy.policy/',
languageInfo: {
autoDetect: true,
fallbackLanguage: 'en',
@ -218,6 +219,7 @@ export const mockSignInExperienceSettings: SignInExperienceResponse = {
color: mockSignInExperience.color,
branding: mockSignInExperience.branding,
termsOfUseUrl: mockSignInExperience.termsOfUseUrl,
privacyPolicyUrl: mockSignInExperience.privacyPolicyUrl,
languageInfo: mockSignInExperience.languageInfo,
signIn: mockSignInExperience.signIn,
signUp: {