Skip to content

VPN Shield App

Full documentation for the VPN Shield tablet app — an encrypted proxy connection system for anonymous browsing.

Overview

The VPN app allows players to connect to encrypted proxy servers for anonymous browsing and roleplay. It supports free and premium proxies, license key activation, and provides exports for other resources to query VPN status.

Architecture

┌─────────────┐     NUI      ┌──────────────┐   TriggerServer   ┌──────────────┐
│  React UI   │ ──────────→  │ client/vpn   │ ───────────────→  │ server/vpn   │
│  (VpnApp)   │              │   .lua       │                   │   .lua       │
│             │ ←──────────  │              │ ←───────────────  │              │
│  proxies[]  │   SendNUI    │  4 callbacks │   vpnData event   │  VpnMgr.*    │
│  connected  │              │              │                   │              │
│  keys[]     │              └──────────────┘                   └──────┬───────┘
└─────────────┘                                                       │
                                                              DB.SetAppData
                                                              DB.GetAppData
                                                              DB.GetAllProxies

                                                              ┌──────▼───────┐
                                                              │   MySQL DB   │
                                                              │              │
                                                              │ wtf_vpn_     │
                                                              │   proxies    │
                                                              │ wtf_vpn_     │
                                                              │   license_   │
                                                              │   keys       │
                                                              │ wtf_tablet_  │
                                                              │   app_data   │
                                                              └──────────────┘

Files

FilePurpose
server/vpn.luaServer logic: proxy management, license keys, connect/disconnect, exports, admin commands
client/vpn.luaNUI callback bridge (5 callbacks) forwarding to server events
ui/src/apps/Vpn/index.jsxReact VPN UI: status bar, proxy lists, key activation, toasts
config/config.luaVPN proxy definitions, admin command settings, darkweb category config
server/database.luaVPN tables (wtf_vpn_proxies, wtf_vpn_license_keys)

Data Flow

1. App Open

User clicks VPN icon
  → VpnApp mounts
  → fetchNui('vpn:getProxies')
  → client/vpn.lua → TriggerServerEvent('wtf_group:server:vpnGetProxies')
  → server/vpn.lua: VpnMgr.GetProxies(source)
    ├── DB.GetAllProxies() → wtf_vpn_proxies
    ├── DB.GetAppData(citizenid, 'vpn', 'connection') → wtf_tablet_app_data
    ├── DB.GetAppData(citizenid, 'vpn', 'keys') → wtf_tablet_app_data
    └── Cleans up expired keys
  → TriggerClientEvent('wtf_group:client:vpnData', src, data)
  → SendNUIMessage({ action: 'vpnData', data })
  → React sets state (proxies, connected, keys)
  → If connected, view auto-sets to 'connected'

2. Connect to Proxy

User clicks "Connect" on Standard VPN
  → fetchNui('vpn:connect', { proxy_id: 'standard' })
  → client/vpn.lua → TriggerServerEvent('wtf_group:server:vpnConnect', 'standard')
  → server/vpn.lua: VpnMgr.Connect(source, 'standard')
    ├── DB.GetProxyById('standard') → validates proxy exists
    ├── Checks not already connected
    ├── If premium: checks keys in wtf_tablet_app_data
    ├── DB.SetAppData(citizenid, 'vpn', 'connection', { proxy_id, proxy_name, connected, expires })
    └── ox_lib:notify → success toast
  → UI reloads → shows green dot, proxy name, Disconnect button

3. Disconnect

User clicks "Disconnect"
  → fetchNui('vpn:disconnect')
  → server/vpn.lua: VpnMgr.Disconnect(source)
    ├── DB.SetAppData(citizenid, 'vpn', 'connection', { connected: false })
    └── ox_lib:notify → "Disconnected from VPN"
  → UI reloads → shows red dot, "Not Connected"

4. Activate License Key

User enters "VPN-AB12-CD34-EF56-GH78" → clicks Activate
  → fetchNui('vpn:activateKey', { key_code: 'VPN-AB12-CD34-EF56-GH78' })
  → server/vpn.lua: VpnMgr.ActivateKey(source, key)
    ├── DB.GetLicenseKey(upperKey) → wtf_vpn_license_keys
    ├── Validates: key exists, used_count < max_uses, not already activated
    ├── Calculates expiry (permanent or duration-based)
    ├── DB.SetAppData(citizenid, 'vpn', 'keys', [...existing, newKey])
    ├── DB.UseLicenseKey(upperKey) → increments used_count
    └── ox_lib:notify → "Key activated! Unlocked: Dark Web Proxy"
  → UI reloads → premium proxy shows "KEY ACTIVE" badge

5. App Store Darkweb Access Check

App Store mounts
  → fetchNui('appstore:checkDarkwebAccess')
  → client/vpn.lua reads local vpnData state
  → Returns { connected: true/false, activeProxyId: 'darkweb'/nil }
  → App Store sets darkwebAccess state
  → If connected to 'darkweb' proxy:
    ├── 💀 Darkweb category tab appears
    ├── Darkweb apps (VPN, Auction) become visible
    ├── Install button enabled
    └── Badge shows "VPN Active"
  → If not connected:
    ├── Darkweb category tab hidden
    ├── Darkweb apps filtered out
    ├── Install button blocked
    └── Badge shows "🔒 VPN Required"

Database Tables

wtf_vpn_proxies

Seeded from Config.VpnProxies on startup.

ColumnTypeDescription
idVARCHAR(50) PKProxy identifier (e.g., standard, darkweb)
nameVARCHAR(100)Display name
descriptionTEXTDescription text
iconVARCHAR(100)Icon identifier
colorVARCHAR(20)Hex color
categoryVARCHAR(50)Free or Premium
is_defaultTINYINT(1)1 = free (no key needed), 0 = premium
sort_orderINTDisplay order

wtf_vpn_license_keys

License keys for premium proxy access.

ColumnTypeDescription
idINT PK AUTOAuto-increment
key_codeVARCHAR(50) UNIQUEKey code (e.g., VPN-AB12-CD34-EF56-GH78)
proxy_idVARCHAR(50)Which proxy this key unlocks
permanentTINYINT(1)1 = never expires, 0 = duration-based
durationINTSeconds until expiry (0 = permanent)
max_usesINTMaximum activations
used_countINTCurrent activations
created_byVARCHAR(50)Creator identifier
created_atBIGINTCreation timestamp

wtf_tablet_app_data (shared)

VPN state stored alongside other app data.

app_iddata_keyValue
vpnconnection{ proxy_id, proxy_name, connected, expires }
vpnkeys[{ key_code, proxy_id, permanent, expires }, ...]

Proxy Configuration

Defined in config/config.lua as Config.VpnProxies:

IDNameCategoryDefault
standardStandard VPNFreeYes
regionalRegional ProxyFreeYes
darkwebDark Web ProxyPremiumNo
militaryMilitary GradePremiumNo
quantumQuantum EncryptedPremiumNo
ghostGhost ProtocolPremiumNo
phantomPhantom ShieldPremiumNo

Server Exports

lua
-- Check if player has ANY VPN active
exports.wtf_group:IsVpnConnected(source) → boolean

-- Check if player is connected to a SPECIFIC proxy
exports.wtf_group:IsPlayerConnectedToProxy(source, 'darkweb') → boolean

-- Get active proxy ID (or nil)
exports.wtf_group:GetPlayerActiveProxy(source) → string|nil

-- Admin: force connect player to proxy
exports.wtf_group:ConnectPlayerToProxy(source, proxyId) → boolean

-- Admin: force disconnect player
exports.wtf_group:DisconnectPlayerProxy(source) → boolean

-- Admin: generate a license key
exports.wtf_group:GenerateLicenseKey(source, proxyId, permanent, duration) → string

Usage Examples

lua
-- Dark market access check
RegisterNetEvent('darkmarket:enter', function()
    local src = source
    if not exports.wtf_group:IsPlayerConnectedToProxy(src, 'darkweb') then
        TriggerClientEvent('ox_lib:notify', src, {
            description = 'You need Dark Web VPN to access this market',
            type = 'error'
        })
        return
    end
    -- Allow access
end)

-- Check any VPN status
local isProtected = exports.wtf_group:IsVpnConnected(source)

-- Get active proxy for RP logging
local proxy = exports.wtf_group:GetPlayerActiveProxy(source)
if proxy then
    print('Player using proxy: ' .. proxy)
end

Admin Commands

CommandUsagePermission
/givevpnkey<serverId> <proxyId> [duration_seconds]admin
/vpnconnect<serverId> <proxyId>admin
/vpndisconnect<serverId>admin

Configuration in config/config.lua:

lua
Config.VpnAdminCommands = {
    enabled = true,
    ace = 'admin',
}

NUI Callbacks

CallbackDataDescription
vpn:getProxiesLoad all proxies + connection state + keys
vpn:connect{ proxy_id }Connect to a proxy
vpn:disconnectDisconnect from VPN
vpn:activateKey{ key_code }Activate a license key
appstore:checkDarkwebAccessCheck if connected to darkweb proxy (for App Store gating)

Client Events

EventDataDescription
wtf_group:client:vpnData{ proxies, connected, activeProxyId, activeProxyName, keys }VPN data response

Server Events

EventParametersDescription
wtf_group:server:vpnGetProxiesRequest VPN data
wtf_group:server:vpnConnectproxyIdConnect to proxy
wtf_group:server:vpnDisconnectDisconnect
wtf_group:server:vpnActivateKeykeyCodeActivate license key

UI Features

  • Status Bar: Green/red dot showing connection status, connected proxy name, Disconnect button
  • Free Proxies: List of free proxies with Connect/Disconnect buttons
  • Premium Proxies: List of premium proxies with Locked/Key Active/Connect states
  • License Key Input: Text input with VPN-XXXX-XXXX-XXXX-XXXX format, Activate button
  • Toast Notifications: Success (green) / Error (red) floating toasts
  • SVG Icons: Custom shield, globe, skull, lock, atom, ghost, eye-off icons per proxy
  • Minimize Persistence: VPN stays connected when minimized/closed; view restores correctly on reopen
  • Darkweb Category: VPN and Auction apps only visible/installable in App Store when connected to darkweb proxy

Darkweb Category

The App Store includes a special "Darkweb" category gated by VPN connection:

Config (config/config.lua):

lua
Config.DarkWeb = {
    requiredProxyId = 'darkweb',   -- Which proxy triggers darkweb access
    apps = { 'vpn', 'auction' },   -- Apps gated by darkweb VPN
}

Behavior:

  • When connected to the darkweb proxy → darkweb category tab appears, apps visible, install enabled
  • When not connected → category hidden, apps filtered out, install blocked
  • Already-installed darkweb apps remain accessible regardless of VPN status
  • Uninstall always works without VPN

App Registry (lib/appRegistry.js):

js
vpn: {
    // ...
    category: 'darkweb',
    darkweb: true,    // Marks as darkweb-gated
}
auction: {
    // ...
    category: 'darkweb',
    darkweb: true,
}

Disabling npwd_vpn

To remove the phone VPN app and use only the tablet version:

  1. Remove or comment out ensure 'npwd_vpn' in server.cfg
  2. The npwd_appstore VPN functions remain (other phone resources may use them)
  3. The tablet VPN is fully independent — no dependency on npwd_appstore

Notes

  • VPN state is purely for RP purposes — no actual network proxy or IP masking
  • State stored in wtf_tablet_app_data (same table used by Notes, Browser, etc.)
  • License keys are single-use by default (max_uses = 1)
  • Premium proxy access requires both: (1) activated key AND (2) key not expired
  • Expired keys are automatically cleaned up on app open
  • VPN stays connected when minimized or closed — no auto-disconnect
  • When reopening VPN app, view auto-restores to connected screen if VPN is active
  • Config.DarkWeb.requiredProxyId must match a proxy ID from Config.VpnProxies

AIFAZI — FiveM Resources