@huloglobal/vendure-plugin-geo-block 0.1.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 +20 -0
- package/LICENSE +34 -0
- package/README.md +146 -0
- package/dist/geo-block.controller.d.ts +56 -0
- package/dist/geo-block.controller.d.ts.map +1 -0
- package/dist/geo-block.controller.js +236 -0
- package/dist/geo-block.controller.js.map +1 -0
- package/dist/geo-regions.d.ts +35 -0
- package/dist/geo-regions.d.ts.map +1 -0
- package/dist/geo-regions.js +62 -0
- package/dist/geo-regions.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin.d.ts +47 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +140 -0
- package/dist/plugin.js.map +1 -0
- package/dist/proxy-headers.d.ts +37 -0
- package/dist/proxy-headers.d.ts.map +1 -0
- package/dist/proxy-headers.js +81 -0
- package/dist/proxy-headers.js.map +1 -0
- package/package.json +41 -0
- package/ui/components/geo-block.component.ts +573 -0
- package/ui/geo-block.module.ts +17 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@huloglobal/vendure-plugin-geo-block` are documented
|
|
4
|
+
here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
|
5
|
+
and this project adheres to [semantic versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [0.1.0] — Unreleased
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- `GeoBlockPlugin` registering five Channel customFields per channel
|
|
11
|
+
(enable toggle, region presets, allowed countries, blocked countries,
|
|
12
|
+
UK region sub-filter).
|
|
13
|
+
- Public `/geo-block/site-config` endpoint serving a flat resolved
|
|
14
|
+
allow-list per channel.
|
|
15
|
+
- Admin endpoints `/geo-block/admin/channels` and `/geo-block/admin/save`.
|
|
16
|
+
- Dedicated admin UI page with mode picker, region preset cards, chip
|
|
17
|
+
pickers, live preview of the resolved allow-list.
|
|
18
|
+
- `resolveAllowedCountries` exported as a pure helper.
|
|
19
|
+
- Licence verification via `@huloglobal/vendure-licence-sdk` with revocation
|
|
20
|
+
polling.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
HULO Global Limited — Commercial Plugin Licence
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 HULO Global Limited. All rights reserved.
|
|
4
|
+
|
|
5
|
+
This software is licensed, not sold, and is made available only to users
|
|
6
|
+
who hold a valid, active subscription or perpetual licence purchased
|
|
7
|
+
from HULO Global Limited.
|
|
8
|
+
|
|
9
|
+
Permitted use:
|
|
10
|
+
|
|
11
|
+
1. The software may be installed and run on a single Vendure
|
|
12
|
+
instance per active subscription (or per perpetual licence). Each
|
|
13
|
+
licence is bound to one or more hostnames named at purchase time.
|
|
14
|
+
|
|
15
|
+
2. The source code is published only so that customers may audit it
|
|
16
|
+
for security review. Modification of the source for production use
|
|
17
|
+
is permitted, but redistribution of the modified source or of any
|
|
18
|
+
derivative work — in whole or in part — is not permitted.
|
|
19
|
+
|
|
20
|
+
Prohibited use:
|
|
21
|
+
|
|
22
|
+
- Use without a valid, active licence key, except for non-production
|
|
23
|
+
evaluation under the plugin's "unlicensed mode" (which writes basic
|
|
24
|
+
delivery rows but disables the open/click endpoints).
|
|
25
|
+
- Use of the package to circumvent the licence verification routine,
|
|
26
|
+
or to extract or replace the embedded RSA public key.
|
|
27
|
+
- Resale, sublicensing, or distribution of the package outside of an
|
|
28
|
+
application that itself holds a valid HULO Vendure plugin licence.
|
|
29
|
+
|
|
30
|
+
No warranty. To the maximum extent permitted by law, HULO Global Limited
|
|
31
|
+
disclaims all warranties and all liability arising from use of this
|
|
32
|
+
software.
|
|
33
|
+
|
|
34
|
+
For commercial licensing enquiries: sales@eliteenterprisesoftware.com
|
package/README.md
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# @huloglobal/vendure-plugin-geo-block
|
|
2
|
+
|
|
3
|
+
Per-channel storefront geo-restriction for Vendure. Allow / block traffic
|
|
4
|
+
by country, by curated region preset (EU, EEA, British Isles, all of
|
|
5
|
+
Europe, North America, Oceania, Worldwide-with-denylist), and by UK
|
|
6
|
+
subdivision (England / Wales / Scotland / Northern Ireland).
|
|
7
|
+
|
|
8
|
+
Maintained by Wayne Garrison.
|
|
9
|
+
|
|
10
|
+
## What you get
|
|
11
|
+
|
|
12
|
+
- **Five clean Channel customFields** registered automatically — the
|
|
13
|
+
consumer doesn't have to repeat them in their `vendure-config.ts`.
|
|
14
|
+
- **Public flat endpoint** at `GET /geo-block/site-config` returning the
|
|
15
|
+
resolved allow-list as JSON, intended to be polled (and cached) by
|
|
16
|
+
the storefront. Identifies the channel via the standard
|
|
17
|
+
`vendure-token` header (same one shop-api uses).
|
|
18
|
+
- **Admin endpoints** at `/geo-block/admin/channels` (read) and
|
|
19
|
+
`/geo-block/admin/save` (write) backing the dedicated admin page.
|
|
20
|
+
- **Dedicated admin UI** under EES Plugins nav: mode-picker (allow
|
|
21
|
+
specific places / allow worldwide except blocked), preset cards
|
|
22
|
+
(one-click region bundles), chip pickers for extras and blocks, live
|
|
23
|
+
preview of the resolved allow-list, UK-region sub-filter when GB is
|
|
24
|
+
resolved as allowed.
|
|
25
|
+
- **Reusable resolver**: `resolveAllowedCountries({ regions,
|
|
26
|
+
extraAllowed, blocked })` is exported as a pure function for
|
|
27
|
+
downstream code.
|
|
28
|
+
|
|
29
|
+
## Install
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
yarn add @huloglobal/vendure-plugin-geo-block
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Wire up
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import { GeoBlockPlugin } from '@huloglobal/vendure-plugin-geo-block';
|
|
39
|
+
|
|
40
|
+
export const config: VendureConfig = {
|
|
41
|
+
plugins: [
|
|
42
|
+
GeoBlockPlugin.init({
|
|
43
|
+
publicBaseUrl: 'https://shop.example.com',
|
|
44
|
+
licenceKey: process.env.HULO_LICENCE_KEY,
|
|
45
|
+
}),
|
|
46
|
+
],
|
|
47
|
+
};
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Add the admin UI extension:
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { GeoBlockPlugin } from '@huloglobal/vendure-plugin-geo-block';
|
|
54
|
+
|
|
55
|
+
compileUiExtensions({
|
|
56
|
+
outputPath: 'admin-ui',
|
|
57
|
+
extensions: [GeoBlockPlugin.uiExtensions /* + your other extensions */],
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Storefront integration
|
|
62
|
+
|
|
63
|
+
The plugin only manages **configuration**. Your storefront enforces the
|
|
64
|
+
block by polling the resolved allow-list and comparing it to the
|
|
65
|
+
visitor's resolved country. A Qwik / Next / Nuxt example using
|
|
66
|
+
MaxMind GeoLite2 for the lookup:
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
// Fetch + cache the per-channel config (60s TTL).
|
|
70
|
+
const cfg = await fetch('https://shop.example.com/geo-block/site-config?token=' + channelToken)
|
|
71
|
+
.then(r => r.json());
|
|
72
|
+
|
|
73
|
+
if (cfg.geoBlock.enabled) {
|
|
74
|
+
const visitorCountry = /* run a MaxMind lookup on the visitor IP */;
|
|
75
|
+
const allowed =
|
|
76
|
+
cfg.geoBlock.allowedCountries === null
|
|
77
|
+
|| cfg.geoBlock.allowedCountries.includes(visitorCountry);
|
|
78
|
+
if (!allowed || cfg.geoBlock.blockedCountries.includes(visitorCountry)) {
|
|
79
|
+
return new Response('Site temporarily unavailable.', { status: 503 });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Behind Cloudflare / nginx / Akamai
|
|
85
|
+
|
|
86
|
+
The geo-block plugin itself doesn't need an IP — it just serves a
|
|
87
|
+
resolved allow-list per channel. The **storefront** does the visitor
|
|
88
|
+
lookup. Two paths to wire it up:
|
|
89
|
+
|
|
90
|
+
### Path 1: use Cloudflare's resolved country header (zero infrastructure)
|
|
91
|
+
|
|
92
|
+
When the "IP Geolocation → Send country data to origin" toggle is on
|
|
93
|
+
in your Cloudflare dashboard (free plan), every request arrives with
|
|
94
|
+
`CF-IPCountry: GB` already populated. Read it directly in your
|
|
95
|
+
storefront:
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
const country = req.headers['cf-ipcountry'] || null;
|
|
99
|
+
if (cfg.geoBlock.enabled && !cfg.geoBlock.allowedCountries?.includes(country)) {
|
|
100
|
+
return new Response('Site temporarily unavailable.', { status: 503 });
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Enable "Send subdivision data" too to also get `CF-Region-Code`
|
|
105
|
+
(`ENG`, `WLS`, `SCT`, `NIR`) for UK subdivision filtering.
|
|
106
|
+
|
|
107
|
+
### Path 2: self-hosted GeoIP lookup (works behind nginx / any proxy)
|
|
108
|
+
|
|
109
|
+
If you're not on Cloudflare, look the visitor IP up via MaxMind
|
|
110
|
+
GeoLite2-City in the storefront. The free `geolite2-redist` npm
|
|
111
|
+
package mirrors the database without requiring a MaxMind account:
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
import { open } from 'geolite2-redist';
|
|
115
|
+
import { Reader } from '@maxmind/geoip2-node';
|
|
116
|
+
|
|
117
|
+
const reader = await open('GeoLite2-City', p => Reader.open(p));
|
|
118
|
+
|
|
119
|
+
// Real IP from nginx / Caddy. The plugin's proxy-headers helper does
|
|
120
|
+
// the same precedence as the email-tracking plugin's IP extractor:
|
|
121
|
+
// cf-connecting-ip → true-client-ip → x-real-ip → x-forwarded-for[0]
|
|
122
|
+
const ip = req.headers['cf-connecting-ip']
|
|
123
|
+
|| req.headers['x-real-ip']
|
|
124
|
+
|| (req.headers['x-forwarded-for'] || '').split(',')[0].trim();
|
|
125
|
+
|
|
126
|
+
const r = reader.city(ip);
|
|
127
|
+
const country = r.country?.isoCode || null;
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Both paths are documented end-to-end in the plugin's example
|
|
131
|
+
storefront snippets at [the README on GitHub](https://github.com/exceeded/vendure-plugin-geo-block).
|
|
132
|
+
|
|
133
|
+
## Init options
|
|
134
|
+
|
|
135
|
+
| Option | Type | Required | Description |
|
|
136
|
+
| --- | --- | --- | --- |
|
|
137
|
+
| `publicBaseUrl` | `string` | yes | Public hostname of your Vendure server. Used in licence host-match. |
|
|
138
|
+
| `licenceKey` | `string` | no* | JWT licence key. Without it `enabled` is forced to `false`. |
|
|
139
|
+
|
|
140
|
+
\* Required for production use. Buy at
|
|
141
|
+
`https://elite-software.co.uk/licence/buy/vendure-plugin-geo-block`.
|
|
142
|
+
|
|
143
|
+
## Licence
|
|
144
|
+
|
|
145
|
+
Commercial — see [LICENSE](./LICENSE). Requires an active subscription
|
|
146
|
+
($9.95/mo) or a perpetual licence.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { RequestContext, TransactionalConnection } from '@vendure/core';
|
|
2
|
+
import { Request, Response } from 'express';
|
|
3
|
+
interface SiteConfig {
|
|
4
|
+
channelToken: string;
|
|
5
|
+
geoBlock: {
|
|
6
|
+
enabled: boolean;
|
|
7
|
+
/** Resolved flat allow-list of ISO 3166-1 alpha-2 country codes
|
|
8
|
+
* the storefront should let through, or `null` meaning "no
|
|
9
|
+
* country restriction" (WORLDWIDE preset). */
|
|
10
|
+
allowedCountries: string[] | null;
|
|
11
|
+
/** Countries the admin always blocks regardless of region. The
|
|
12
|
+
* resolved `allowedCountries` already has these subtracted —
|
|
13
|
+
* this field is exposed for diagnostics + so the storefront can
|
|
14
|
+
* enforce it even when allowedCountries is `null`. */
|
|
15
|
+
blockedCountries: string[];
|
|
16
|
+
/** UK subdivisions that must additionally match when the visitor
|
|
17
|
+
* resolves to GB. Empty = "any UK region allowed". */
|
|
18
|
+
allowedGbRegions: string[];
|
|
19
|
+
/** The raw region presets the admin selected — exposed for
|
|
20
|
+
* diagnostics (and so the frontend can display the admin's
|
|
21
|
+
* intent in any debug UI). */
|
|
22
|
+
allowedRegions: string[];
|
|
23
|
+
};
|
|
24
|
+
companyData: {
|
|
25
|
+
showCompanyNumber: boolean;
|
|
26
|
+
companyNumber: string;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Public endpoint serving the storefront-facing site config for a given
|
|
31
|
+
* channel. The storefront calls GET /ees/site-config and identifies the
|
|
32
|
+
* channel via the standard `vendure-token` header (the same one shop-api
|
|
33
|
+
* uses), or via ?token=<channelToken> as a fallback for static fetchers.
|
|
34
|
+
*
|
|
35
|
+
* Returned shape is deliberately small + flat so the frontend can cache
|
|
36
|
+
* it in module memory and re-fetch every 60s without much overhead.
|
|
37
|
+
*/
|
|
38
|
+
export declare class GeoBlockController {
|
|
39
|
+
private connection;
|
|
40
|
+
constructor(connection: TransactionalConnection);
|
|
41
|
+
getConfig(req: Request): Promise<SiteConfig>;
|
|
42
|
+
/**
|
|
43
|
+
* Admin: list every channel with its current geo-block settings.
|
|
44
|
+
* Used by the dedicated Vendure admin page so the admin can switch
|
|
45
|
+
* between Elite / LicenseDock / any future channel from a single
|
|
46
|
+
* screen.
|
|
47
|
+
*/
|
|
48
|
+
listChannels(ctx: RequestContext, res: Response): Promise<Response<any, Record<string, any>> | undefined>;
|
|
49
|
+
/**
|
|
50
|
+
* Admin: save the geo-block settings for one channel. Body shape
|
|
51
|
+
* mirrors what `listChannels` returns (minus the resolved preview).
|
|
52
|
+
*/
|
|
53
|
+
saveChannel(ctx: RequestContext, body: any, res: Response): Promise<Response<any, Record<string, any>> | undefined>;
|
|
54
|
+
}
|
|
55
|
+
export {};
|
|
56
|
+
//# sourceMappingURL=geo-block.controller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"geo-block.controller.d.ts","sourceRoot":"","sources":["../src/geo-block.controller.ts"],"names":[],"mappings":"AACA,OAAO,EAAmB,cAAc,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AACzF,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAgB5C,UAAU,UAAU;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE;QACN,OAAO,EAAE,OAAO,CAAC;QACjB;;uDAE+C;QAC/C,gBAAgB,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;QAClC;;;+DAGuD;QACvD,gBAAgB,EAAE,MAAM,EAAE,CAAC;QAC3B;+DACuD;QACvD,gBAAgB,EAAE,MAAM,EAAE,CAAC;QAC3B;;uCAE+B;QAC/B,cAAc,EAAE,MAAM,EAAE,CAAC;KAC5B,CAAC;IACF,WAAW,EAAE;QACT,iBAAiB,EAAE,OAAO,CAAC;QAC3B,aAAa,EAAE,MAAM,CAAC;KACzB,CAAC;CACL;AAcD;;;;;;;;GAQG;AACH,qBACa,kBAAkB;IACf,OAAO,CAAC,UAAU;gBAAV,UAAU,EAAE,uBAAuB;IAGjD,SAAS,CAAQ,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC;IAqEzD;;;;;OAKG;IAEG,YAAY,CAAQ,GAAG,EAAE,cAAc,EAAS,GAAG,EAAE,QAAQ;IA6CnE;;;OAGG;IAEG,WAAW,CAAQ,GAAG,EAAE,cAAc,EAAU,IAAI,EAAE,GAAG,EAAS,GAAG,EAAE,QAAQ;CA8BxF"}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.GeoBlockController = void 0;
|
|
16
|
+
const common_1 = require("@nestjs/common");
|
|
17
|
+
const core_1 = require("@vendure/core");
|
|
18
|
+
const geo_regions_1 = require("./geo-regions");
|
|
19
|
+
function requireAdmin(ctx, res, write = false) {
|
|
20
|
+
if (!(ctx === null || ctx === void 0 ? void 0 : ctx.activeUserId)) {
|
|
21
|
+
res.status(401).json({ error: 'Authentication required' });
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
const needed = write ? [core_1.Permission.UpdateCatalog] : [core_1.Permission.ReadCatalog];
|
|
25
|
+
if (!ctx.userHasPermissions(needed)) {
|
|
26
|
+
res.status(403).json({ error: 'Insufficient permissions' });
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
const DEFAULTS = {
|
|
32
|
+
channelToken: '',
|
|
33
|
+
geoBlock: {
|
|
34
|
+
enabled: false,
|
|
35
|
+
allowedCountries: ['GB'],
|
|
36
|
+
blockedCountries: [],
|
|
37
|
+
allowedGbRegions: ['ENG', 'WLS'],
|
|
38
|
+
allowedRegions: [],
|
|
39
|
+
},
|
|
40
|
+
companyData: { showCompanyNumber: false, companyNumber: '' },
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Public endpoint serving the storefront-facing site config for a given
|
|
44
|
+
* channel. The storefront calls GET /ees/site-config and identifies the
|
|
45
|
+
* channel via the standard `vendure-token` header (the same one shop-api
|
|
46
|
+
* uses), or via ?token=<channelToken> as a fallback for static fetchers.
|
|
47
|
+
*
|
|
48
|
+
* Returned shape is deliberately small + flat so the frontend can cache
|
|
49
|
+
* it in module memory and re-fetch every 60s without much overhead.
|
|
50
|
+
*/
|
|
51
|
+
let GeoBlockController = class GeoBlockController {
|
|
52
|
+
constructor(connection) {
|
|
53
|
+
this.connection = connection;
|
|
54
|
+
}
|
|
55
|
+
async getConfig(req) {
|
|
56
|
+
const headerToken = req.headers['vendure-token']
|
|
57
|
+
|| req.headers['x-vendure-token']
|
|
58
|
+
|| req.query.token
|
|
59
|
+
|| '';
|
|
60
|
+
const token = headerToken.trim();
|
|
61
|
+
if (!token) {
|
|
62
|
+
return { ...DEFAULTS };
|
|
63
|
+
}
|
|
64
|
+
const rows = await this.connection.rawConnection.query(`SELECT token,
|
|
65
|
+
customFieldsShowcompanynumber AS showCompanyNumber,
|
|
66
|
+
customFieldsBusinesscompanynumber AS companyNumber,
|
|
67
|
+
customFieldsGeoblockenabled AS geoBlockEnabled,
|
|
68
|
+
customFieldsGeoblockallowedregions AS allowedRegions,
|
|
69
|
+
customFieldsGeoblockallowedcountries AS extraAllowed,
|
|
70
|
+
customFieldsGeoblockblockedcountries AS blockedCountries,
|
|
71
|
+
customFieldsGeoblockallowedgbregions AS allowedGbRegions
|
|
72
|
+
FROM channel
|
|
73
|
+
WHERE token = ?
|
|
74
|
+
LIMIT 1`, [token]);
|
|
75
|
+
if (!rows.length) {
|
|
76
|
+
return { ...DEFAULTS, channelToken: token };
|
|
77
|
+
}
|
|
78
|
+
const r = rows[0];
|
|
79
|
+
const parseList = (raw, fallback) => {
|
|
80
|
+
if (Array.isArray(raw))
|
|
81
|
+
return raw.filter(v => typeof v === 'string');
|
|
82
|
+
if (typeof raw !== 'string' || !raw.length)
|
|
83
|
+
return fallback;
|
|
84
|
+
try {
|
|
85
|
+
const parsed = JSON.parse(raw);
|
|
86
|
+
if (Array.isArray(parsed))
|
|
87
|
+
return parsed.filter(v => typeof v === 'string');
|
|
88
|
+
}
|
|
89
|
+
catch { }
|
|
90
|
+
// MariaDB stores list customFields as a JSON-encoded text. Be
|
|
91
|
+
// forgiving of a CSV fallback for older rows.
|
|
92
|
+
return raw.split(',').map(s => s.trim()).filter(Boolean);
|
|
93
|
+
};
|
|
94
|
+
const allowedRegions = parseList(r.allowedRegions, []);
|
|
95
|
+
const extraAllowed = parseList(r.extraAllowed, ['GB']);
|
|
96
|
+
const blockedCountries = parseList(r.blockedCountries, []);
|
|
97
|
+
// Resolve regions + extras − blocked into a single flat allow-list.
|
|
98
|
+
// `allowed === null` means WORLDWIDE — no country filter, only the
|
|
99
|
+
// denylist applies.
|
|
100
|
+
const resolved = (0, geo_regions_1.resolveAllowedCountries)({
|
|
101
|
+
regions: allowedRegions,
|
|
102
|
+
extraAllowed,
|
|
103
|
+
blocked: blockedCountries,
|
|
104
|
+
});
|
|
105
|
+
return {
|
|
106
|
+
channelToken: token,
|
|
107
|
+
geoBlock: {
|
|
108
|
+
enabled: !!Number(r.geoBlockEnabled || 0),
|
|
109
|
+
allowedCountries: resolved.allowed,
|
|
110
|
+
blockedCountries: resolved.blocked,
|
|
111
|
+
allowedGbRegions: parseList(r.allowedGbRegions, ['ENG', 'WLS']),
|
|
112
|
+
allowedRegions,
|
|
113
|
+
},
|
|
114
|
+
companyData: {
|
|
115
|
+
showCompanyNumber: !!Number(r.showCompanyNumber || 0),
|
|
116
|
+
companyNumber: String(r.companyNumber || ''),
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Admin: list every channel with its current geo-block settings.
|
|
122
|
+
* Used by the dedicated Vendure admin page so the admin can switch
|
|
123
|
+
* between Elite / LicenseDock / any future channel from a single
|
|
124
|
+
* screen.
|
|
125
|
+
*/
|
|
126
|
+
async listChannels(ctx, res) {
|
|
127
|
+
if (!requireAdmin(ctx, res, false))
|
|
128
|
+
return;
|
|
129
|
+
const rows = await this.connection.rawConnection.query(`SELECT id, code, token,
|
|
130
|
+
COALESCE(customFieldsGeoblockenabled, 0) AS geoBlockEnabled,
|
|
131
|
+
customFieldsGeoblockallowedregions AS allowedRegions,
|
|
132
|
+
customFieldsGeoblockallowedcountries AS extraAllowed,
|
|
133
|
+
customFieldsGeoblockblockedcountries AS blockedCountries,
|
|
134
|
+
customFieldsGeoblockallowedgbregions AS allowedGbRegions
|
|
135
|
+
FROM channel
|
|
136
|
+
ORDER BY id`);
|
|
137
|
+
const parseList = (raw, fallback) => {
|
|
138
|
+
if (Array.isArray(raw))
|
|
139
|
+
return raw.filter(v => typeof v === 'string');
|
|
140
|
+
if (typeof raw !== 'string' || !raw.length)
|
|
141
|
+
return fallback;
|
|
142
|
+
try {
|
|
143
|
+
const parsed = JSON.parse(raw);
|
|
144
|
+
if (Array.isArray(parsed))
|
|
145
|
+
return parsed.filter(v => typeof v === 'string');
|
|
146
|
+
}
|
|
147
|
+
catch { }
|
|
148
|
+
return raw.split(',').map(s => s.trim()).filter(Boolean);
|
|
149
|
+
};
|
|
150
|
+
const result = rows.map((r) => {
|
|
151
|
+
const allowedRegions = parseList(r.allowedRegions, []);
|
|
152
|
+
const extraAllowed = parseList(r.extraAllowed, []);
|
|
153
|
+
const blockedCountries = parseList(r.blockedCountries, []);
|
|
154
|
+
const resolved = (0, geo_regions_1.resolveAllowedCountries)({
|
|
155
|
+
regions: allowedRegions,
|
|
156
|
+
extraAllowed,
|
|
157
|
+
blocked: blockedCountries,
|
|
158
|
+
});
|
|
159
|
+
return {
|
|
160
|
+
id: r.id,
|
|
161
|
+
code: r.code,
|
|
162
|
+
token: r.token,
|
|
163
|
+
enabled: !!Number(r.geoBlockEnabled || 0),
|
|
164
|
+
allowedRegions,
|
|
165
|
+
extraAllowed,
|
|
166
|
+
blockedCountries,
|
|
167
|
+
allowedGbRegions: parseList(r.allowedGbRegions, []),
|
|
168
|
+
resolved: { allowedCountries: resolved.allowed, blockedCountries: resolved.blocked },
|
|
169
|
+
};
|
|
170
|
+
});
|
|
171
|
+
return res.json({ channels: result });
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Admin: save the geo-block settings for one channel. Body shape
|
|
175
|
+
* mirrors what `listChannels` returns (minus the resolved preview).
|
|
176
|
+
*/
|
|
177
|
+
async saveChannel(ctx, body, res) {
|
|
178
|
+
if (!requireAdmin(ctx, res, true))
|
|
179
|
+
return;
|
|
180
|
+
if (!(body === null || body === void 0 ? void 0 : body.token) || typeof body.token !== 'string') {
|
|
181
|
+
return res.status(400).json({ error: 'channel token required' });
|
|
182
|
+
}
|
|
183
|
+
const normList = (v) => {
|
|
184
|
+
if (!Array.isArray(v))
|
|
185
|
+
return '[]';
|
|
186
|
+
const clean = v.filter(x => typeof x === 'string').map(x => x.trim().toUpperCase()).filter(Boolean);
|
|
187
|
+
return JSON.stringify(Array.from(new Set(clean)));
|
|
188
|
+
};
|
|
189
|
+
const enabled = body.enabled ? 1 : 0;
|
|
190
|
+
const regions = normList(body.allowedRegions);
|
|
191
|
+
const extras = normList(body.extraAllowed);
|
|
192
|
+
const blocked = normList(body.blockedCountries);
|
|
193
|
+
const gbRegions = normList(body.allowedGbRegions);
|
|
194
|
+
const updated = await this.connection.rawConnection.query(`UPDATE channel
|
|
195
|
+
SET customFieldsGeoblockenabled = ?,
|
|
196
|
+
customFieldsGeoblockallowedregions = ?,
|
|
197
|
+
customFieldsGeoblockallowedcountries = ?,
|
|
198
|
+
customFieldsGeoblockblockedcountries = ?,
|
|
199
|
+
customFieldsGeoblockallowedgbregions = ?
|
|
200
|
+
WHERE token = ?`, [enabled, regions, extras, blocked, gbRegions, body.token]);
|
|
201
|
+
if (!updated.affectedRows) {
|
|
202
|
+
return res.status(404).json({ error: 'channel not found' });
|
|
203
|
+
}
|
|
204
|
+
return res.json({ ok: true });
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
exports.GeoBlockController = GeoBlockController;
|
|
208
|
+
__decorate([
|
|
209
|
+
(0, common_1.Get)('site-config'),
|
|
210
|
+
__param(0, (0, common_1.Req)()),
|
|
211
|
+
__metadata("design:type", Function),
|
|
212
|
+
__metadata("design:paramtypes", [Object]),
|
|
213
|
+
__metadata("design:returntype", Promise)
|
|
214
|
+
], GeoBlockController.prototype, "getConfig", null);
|
|
215
|
+
__decorate([
|
|
216
|
+
(0, common_1.Get)('admin/channels'),
|
|
217
|
+
__param(0, (0, core_1.Ctx)()),
|
|
218
|
+
__param(1, (0, common_1.Res)()),
|
|
219
|
+
__metadata("design:type", Function),
|
|
220
|
+
__metadata("design:paramtypes", [core_1.RequestContext, Object]),
|
|
221
|
+
__metadata("design:returntype", Promise)
|
|
222
|
+
], GeoBlockController.prototype, "listChannels", null);
|
|
223
|
+
__decorate([
|
|
224
|
+
(0, common_1.Post)('admin/save'),
|
|
225
|
+
__param(0, (0, core_1.Ctx)()),
|
|
226
|
+
__param(1, (0, common_1.Body)()),
|
|
227
|
+
__param(2, (0, common_1.Res)()),
|
|
228
|
+
__metadata("design:type", Function),
|
|
229
|
+
__metadata("design:paramtypes", [core_1.RequestContext, Object, Object]),
|
|
230
|
+
__metadata("design:returntype", Promise)
|
|
231
|
+
], GeoBlockController.prototype, "saveChannel", null);
|
|
232
|
+
exports.GeoBlockController = GeoBlockController = __decorate([
|
|
233
|
+
(0, common_1.Controller)('geo-block'),
|
|
234
|
+
__metadata("design:paramtypes", [core_1.TransactionalConnection])
|
|
235
|
+
], GeoBlockController);
|
|
236
|
+
//# sourceMappingURL=geo-block.controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"geo-block.controller.js","sourceRoot":"","sources":["../src/geo-block.controller.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Region presets for the storefront geo-block.
|
|
3
|
+
*
|
|
4
|
+
* Each key is the value stored in the channel customField
|
|
5
|
+
* `geoBlockAllowedRegions`; the array is the ISO 3166-1 alpha-2
|
|
6
|
+
* country list it expands to. The site-config resolver unions the
|
|
7
|
+
* countries from every selected region, adds the admin's "extra
|
|
8
|
+
* allowed countries", then removes anything in the "blocked
|
|
9
|
+
* countries" list — giving a single flat allow-list the storefront
|
|
10
|
+
* checks each visitor against.
|
|
11
|
+
*
|
|
12
|
+
* `WORLDWIDE` is the special "no country filter" preset (still
|
|
13
|
+
* subject to the blocked-countries list, so the admin can ship
|
|
14
|
+
* "everywhere except these few" by picking WORLDWIDE + a denylist).
|
|
15
|
+
*/
|
|
16
|
+
export type GeoRegionKey = 'EUROPE' | 'EU' | 'EEA' | 'BRITISH_ISLES' | 'UK_ONLY' | 'NORTH_AMERICA' | 'OCEANIA' | 'WORLDWIDE';
|
|
17
|
+
/**
|
|
18
|
+
* Resolve an admin's geo-block selections into a final country
|
|
19
|
+
* allow-list (or `null` meaning "allow any country").
|
|
20
|
+
*
|
|
21
|
+
* final = (∪ region.expand for r in regions) ∪ extraAllowed − blocked
|
|
22
|
+
*
|
|
23
|
+
* Returns `null` if WORLDWIDE is among the selected regions AND there's
|
|
24
|
+
* no other constraint that would make the resolved set finite — the
|
|
25
|
+
* caller then skips the country check and only applies the denylist.
|
|
26
|
+
*/
|
|
27
|
+
export declare function resolveAllowedCountries(input: {
|
|
28
|
+
regions: string[];
|
|
29
|
+
extraAllowed: string[];
|
|
30
|
+
blocked: string[];
|
|
31
|
+
}): {
|
|
32
|
+
allowed: string[] | null;
|
|
33
|
+
blocked: string[];
|
|
34
|
+
};
|
|
35
|
+
//# sourceMappingURL=geo-regions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"geo-regions.d.ts","sourceRoot":"","sources":["../src/geo-regions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,YAAY,GAClB,QAAQ,GAAG,IAAI,GAAG,KAAK,GAAG,eAAe,GAAG,SAAS,GACrD,eAAe,GAAG,SAAS,GAAG,WAAW,CAAC;AAgChD;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE;IAC3C,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,OAAO,EAAE,MAAM,EAAE,CAAC;CACrB,GAAG;IAAE,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAmBlD"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveAllowedCountries = resolveAllowedCountries;
|
|
4
|
+
const EU_27 = [
|
|
5
|
+
'AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR', 'DE',
|
|
6
|
+
'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL', 'PL', 'PT',
|
|
7
|
+
'RO', 'SK', 'SI', 'ES', 'SE',
|
|
8
|
+
];
|
|
9
|
+
const EEA_EXTRA = ['IS', 'LI', 'NO']; // EEA non-EU members
|
|
10
|
+
const NON_EU_EUROPE = [
|
|
11
|
+
// The rest of geographic Europe — micro-states, Western Balkans,
|
|
12
|
+
// Eastern Europe. Russia/Belarus included so the admin has a
|
|
13
|
+
// single switch + the freedom to add them to the denylist if
|
|
14
|
+
// they don't want to sell there.
|
|
15
|
+
'GB', 'CH', 'NO', 'IS', 'LI',
|
|
16
|
+
'AL', 'AD', 'BA', 'BY', 'FO', 'GI', 'IM', 'JE', 'GG',
|
|
17
|
+
'MC', 'ME', 'MD', 'MK', 'RS', 'SM', 'UA', 'VA', 'XK', 'RU',
|
|
18
|
+
];
|
|
19
|
+
const EUROPE = Array.from(new Set([...EU_27, ...NON_EU_EUROPE]));
|
|
20
|
+
const REGION_TO_COUNTRIES = {
|
|
21
|
+
EUROPE,
|
|
22
|
+
EU: EU_27,
|
|
23
|
+
EEA: Array.from(new Set([...EU_27, ...EEA_EXTRA])),
|
|
24
|
+
BRITISH_ISLES: ['GB', 'IE', 'IM', 'JE', 'GG', 'FO'],
|
|
25
|
+
UK_ONLY: ['GB'],
|
|
26
|
+
NORTH_AMERICA: ['US', 'CA', 'MX'],
|
|
27
|
+
OCEANIA: ['AU', 'NZ'],
|
|
28
|
+
WORLDWIDE: null, // null = no country restriction; denylist still applies
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Resolve an admin's geo-block selections into a final country
|
|
32
|
+
* allow-list (or `null` meaning "allow any country").
|
|
33
|
+
*
|
|
34
|
+
* final = (∪ region.expand for r in regions) ∪ extraAllowed − blocked
|
|
35
|
+
*
|
|
36
|
+
* Returns `null` if WORLDWIDE is among the selected regions AND there's
|
|
37
|
+
* no other constraint that would make the resolved set finite — the
|
|
38
|
+
* caller then skips the country check and only applies the denylist.
|
|
39
|
+
*/
|
|
40
|
+
function resolveAllowedCountries(input) {
|
|
41
|
+
const norm = (s) => s.trim().toUpperCase();
|
|
42
|
+
const regions = (input.regions || []).map(norm);
|
|
43
|
+
const extra = (input.extraAllowed || []).map(norm);
|
|
44
|
+
const blocked = (input.blocked || []).map(norm);
|
|
45
|
+
// Worldwide short-circuits — denylist is the only constraint.
|
|
46
|
+
if (regions.includes('WORLDWIDE')) {
|
|
47
|
+
return { allowed: null, blocked };
|
|
48
|
+
}
|
|
49
|
+
const allowed = new Set();
|
|
50
|
+
for (const r of regions) {
|
|
51
|
+
const countries = REGION_TO_COUNTRIES[r];
|
|
52
|
+
if (countries)
|
|
53
|
+
for (const c of countries)
|
|
54
|
+
allowed.add(c);
|
|
55
|
+
}
|
|
56
|
+
for (const c of extra)
|
|
57
|
+
allowed.add(c);
|
|
58
|
+
for (const c of blocked)
|
|
59
|
+
allowed.delete(c);
|
|
60
|
+
return { allowed: Array.from(allowed).sort(), blocked };
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=geo-regions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"geo-regions.js","sourceRoot":"","sources":["../src/geo-regions.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@huloglobal/vendure-plugin-geo-block` — public exports.
|
|
3
|
+
*
|
|
4
|
+
* `GeoBlockPlugin` registers the per-channel custom fields, controllers
|
|
5
|
+
* and admin UI. `resolveAllowedCountries` is also re-exported so
|
|
6
|
+
* downstream plugins / scripts can compute the resolved country
|
|
7
|
+
* allow-list outside the HTTP path.
|
|
8
|
+
*/
|
|
9
|
+
export { GeoBlockPlugin, GeoBlockPluginOptions } from './plugin';
|
|
10
|
+
export { resolveAllowedCountries, GeoRegionKey } from './geo-regions';
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AACjE,OAAO,EAAE,uBAAuB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `@huloglobal/vendure-plugin-geo-block` — public exports.
|
|
4
|
+
*
|
|
5
|
+
* `GeoBlockPlugin` registers the per-channel custom fields, controllers
|
|
6
|
+
* and admin UI. `resolveAllowedCountries` is also re-exported so
|
|
7
|
+
* downstream plugins / scripts can compute the resolved country
|
|
8
|
+
* allow-list outside the HTTP path.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.resolveAllowedCountries = exports.GeoBlockPlugin = void 0;
|
|
12
|
+
var plugin_1 = require("./plugin");
|
|
13
|
+
Object.defineProperty(exports, "GeoBlockPlugin", { enumerable: true, get: function () { return plugin_1.GeoBlockPlugin; } });
|
|
14
|
+
var geo_regions_1 = require("./geo-regions");
|
|
15
|
+
Object.defineProperty(exports, "resolveAllowedCountries", { enumerable: true, get: function () { return geo_regions_1.resolveAllowedCountries; } });
|
|
16
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;AAEH,mCAAiE;AAAxD,wGAAA,cAAc,OAAA;AACvB,6CAAsE;AAA7D,sHAAA,uBAAuB,OAAA"}
|