定制 greghanton/simplifi-hq-oauth-api 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

greghanton/simplifi-hq-oauth-api

Composer 安装命令:

composer require greghanton/simplifi-hq-oauth-api

包简介

OAuth2 client and request dispatcher for the Joy Pilot API tier

README 文档

README

OAuth2 client and request dispatcher for the Joy Pilot API tier.

Install

composer require greghanton/simplifi-hq-oauth-api

Configuration

This package loads defaults from config.php and then reads values from environment variables using simplifiHqOauthApiLibEnv().

Environment variables

Variable Required Default Notes
SIMPLIFI_API_GRANT_TYPE No client_credentials The only grant this package itself can mint. The password grant is disabled server-side (not just deprecated) — see Per-consumer grant matrix.
SIMPLIFI_API_CLIENT_ID Yes - OAuth client id.
SIMPLIFI_API_CLIENT_SECRET Yes - OAuth client secret.
SIMPLIFI_API_USERNAME No (legacy) - Dead unless a future grant reintroduces it; the password grant that read this is disabled server-side.
SIMPLIFI_API_PASSWORD No (legacy) - Dead alongside SIMPLIFI_API_USERNAME, same reason.
SIMPLIFI_API_SCOPE No * OAuth scope, forwarded as-is to /oauth/token.
SIMPLIFI_API_ALLOW_SERVICE_MINT No true Set false on any per-session/user-context config (see User-scoped tokens). Leave true (default) on the global/anonymous config.
SIMPLIFI_API_URL_BASE Yes - API base URL, e.g. https://api.example.com/.
SIMPLIFI_API_ACCESS_TOKEN_STORE_AS No temp_file custom is recommended for production, temp_file is fine for local/dev.
SIMPLIFI_API_ACCESS_TOKEN_TEMP_FILE_FILENAME No Derived from project path/version Only used when store_as=temp_file.
SIMPLIFI_API_ACCESS_TOKEN_CUSTOM_KEY For custom store simplifi-hq-oauth-api-access-token Key passed to custom callables.
SIMPLIFI_API_ACCESS_TOKEN_GET For custom store - JSON-encoded callable. Signature: get($customKey): ?string.
SIMPLIFI_API_ACCESS_TOKEN_SET For custom store - JSON-encoded callable. Signature: set($customKey, $tokenJson): mixed.
SIMPLIFI_API_ACCESS_TOKEN_DEL For custom store - JSON-encoded callable. Signature: del($customKey): mixed.
SIMPLIFI_API_ACCESS_TOKEN_LOCK Optional - JSON-encoded callable. Signature: lock($customKey, $ttlSeconds): bool.
SIMPLIFI_API_ACCESS_TOKEN_UNLOCK Optional - JSON-encoded callable. Signature: unlock($customKey): mixed.
SIMPLIFI_API_ERROR_LOG_FUNCTION No "error_log" JSON-encoded callable for internal error logging.
SIMPLIFI_API_DEFAULT_HEADERS No [] JSON object of headers merged into each request.
SIMPLIFI_API_SSL_VERIFY No true TLS certificate verification for HTTP requests.
SIMPLIFI_API_ADD_TRACE_DEBUG_HEADER No false Adds trace-debug-header from caller backtrace.

Basic usage

Synchronous request

<?php

require __DIR__.'/vendor/autoload.php';

use SimplifiApi\ApiRequest;

$response = ApiRequest::request([
    'method' => 'GET',
    'url' => 'sales',
]);

if (! $response->success()) {
    throw new RuntimeException($response->errorsToString());
}

foreach ($response as $row) {
    // ApiResponse implements Iterator when response has a top-level "data" array.
    var_dump($row);
}

Async request

<?php

use SimplifiApi\ApiRequest;

$promise = ApiRequest::requestAsync([
    'method' => 'GET',
    'url' => 'sales',
]);

$response = $promise->wait();

if ($response->success()) {
    var_dump($response->response());
}

Batch request

<?php

use SimplifiApi\ApiRequest;

$responses = ApiRequest::batch([
    ['method' => 'GET', 'url' => 'sales'],
    ['method' => 'GET', 'url' => ['sales/$/invoice', 102]],
]);

foreach ($responses as $response) {
    if (! $response->success()) {
        error_log($response->errorsToString());
    }
}

Access token storage

Supported modes:

  • custom (recommended for production; usually Redis)
  • temp_file (unchanged local/dev fallback)

Redis setup via custom callables

SIMPLIFI_API_ACCESS_TOKEN_STORE_AS=custom
SIMPLIFI_API_ACCESS_TOKEN_CUSTOM_KEY=simplifi-hq-oauth-api-access-token
SIMPLIFI_API_ACCESS_TOKEN_GET="[\"\\\\App\\\\Cache\\\\TokenStore\", \"get\"]"
SIMPLIFI_API_ACCESS_TOKEN_SET="[\"\\\\App\\\\Cache\\\\TokenStore\", \"set\"]"
SIMPLIFI_API_ACCESS_TOKEN_DEL="[\"\\\\App\\\\Cache\\\\TokenStore\", \"del\"]"

Laravel example:

<?php

namespace App\Cache;

use Illuminate\Support\Facades\Cache;

class TokenStore
{
    public static function get(string $key): ?string
    {
        return Cache::store('redis')->get($key);
    }

    public static function set(string $key, string $value): bool
    {
        return Cache::store('redis')->forever($key, $value);
    }

    public static function del(string $key): bool
    {
        return Cache::store('redis')->forget($key);
    }
}

Token refresh mutex (optional, recommended in production)

When several processes see an expired token at the same time, configure lock/unlock callables to prevent duplicate oauth/token refresh requests.

SIMPLIFI_API_ACCESS_TOKEN_LOCK="[\"\\\\App\\\\Cache\\\\TokenLock\", \"acquire\"]"
SIMPLIFI_API_ACCESS_TOKEN_UNLOCK="[\"\\\\App\\\\Cache\\\\TokenLock\", \"release\"]"

Laravel lock example:

<?php

namespace App\Cache;

use Illuminate\Support\Facades\Cache;

class TokenLock
{
    private static array $locks = [];

    public static function acquire(string $customKey, int $ttl): bool
    {
        $lockName = $customKey.':oauth-refresh-lock';
        $lock = Cache::store('redis')->lock($lockName, $ttl);

        if (! $lock->get()) {
            return false;
        }

        self::$locks[$lockName] = $lock;

        return true;
    }

    public static function release(string $customKey): void
    {
        $lockName = $customKey.':oauth-refresh-lock';

        if (! isset(self::$locks[$lockName])) {
            return;
        }

        self::$locks[$lockName]->release();
        unset(self::$locks[$lockName]);
    }
}

Mutex flow inside the package:

  1. Try to lock (currently with ~10s TTL).
  2. If lock is acquired, refresh token and unlock.
  3. If lock is not acquired, wait 1-2s, re-check cache, and reuse token if present.
  4. Only fetch a fresh token if cache is still empty after waiting.

Event listener examples

Use listeners for metrics, tracing, or debugging without changing package internals.

<?php

use SimplifiApi\ApiRequest;
use SimplifiApi\ApiResponse;

ApiRequest::addEventListener(ApiRequest::EVENT_BEFORE_REQUEST, function (array $requestOptions, array $config): void {
    // Example: emit request-start metric.
});

ApiRequest::addEventListener(ApiRequest::EVENT_AFTER_REGULAR_REQUEST, function (ApiResponse $response): void {
    // Example: emit sync request timing metric.
});

ApiRequest::addEventListener(ApiRequest::EVENT_AFTER_ASYNC_REQUEST, function (ApiResponse $response): void {
    // Example: emit async request metric.
});

ApiRequest::addEventListener(ApiRequest::EVENT_AFTER_BATCH_REQUEST, function (ApiResponse $response): void {
    // Example: emit per-item batch metric.
});

ApiResponse::addEventListener(ApiResponse::EVENT_RESPONSE_CREATED, function (ApiResponse $response): void {
    // Example: central hook after any response object is created.
});

Laravel config:cache gotcha

src/helpers.php resolves env values in this order:

  1. simplifiHqOauthApiEnv($key, $default) if your app defines it
  2. fallback to Laravel env($key, $default) if available
  3. fallback default

With Laravel config:cache, runtime env() calls outside config files return null. To keep custom token store and mutex callables reliable under cached config, define simplifiHqOauthApiEnv() in your app and read from your own config layer instead of direct runtime env().

Per-consumer grant matrix

The API tier's identity model (Stage 1.5) draws a hard line between three kinds of token, and this package only ever mints one of them. Use this table to decide how each consumer should be configured:

Consumer Grant this package mints How the token is bound Notes
Anonymous server-to-server (password reset, signup, email verification, the public login/onboarding front doors) client_credentials via SIMPLIFI_API_GRANT_TYPE=client_credentials No actor — app-level only This is the only flow this package's AccessToken::generateNewAccessToken() performs. Use a narrowed service scope (e.g. svc:auth/svc:onboarding), not *, where the client supports it. Cached under the global custom_key.
First-party user-scoped (a logged-in GUI/No Worries/Apex Wealth session) Not minted by this package. The consumer calls POST /oauth/token directly with the API's login_token grant (exchanging the one-time login token issued at login) to get the user's access token, and grant_type=refresh_token to renew it. Per-session — the consumer writes the token into a per-session custom_key (see User-scoped tokens) password is disabled server-side — do not configure it for this case. This package only ever reads the token the consumer already obtained.
Third-party / MCP / AI integrations Not via this package at all authorization_code + PKCE, entity-bound These consumers talk to Passport's authorization-code flow directly; listed here for completeness only.

User-scoped tokens: the package never mints them

Per the Stage 1.5 identity model, the GUI/consumer owns acquiring and refreshing user-scoped tokens; this package only owns using them. Concretely:

  1. At login, the consumer exchanges its one-time login token for a user-scoped access + refresh token pair directly against /oauth/token (grant_type=login_token), outside this package.
  2. The consumer writes that token into a per-session custom store — i.e. a custom_key that is unique per user session, not the shared service-token key — using the same get/set/del callable hooks described above (Redis setup).
  3. The consumer calls ApiRequest::request($options, $overrideConfig) with $overrideConfig pointing access_token.custom.custom_key (and get/set/del) at that per-session store.
  4. $overrideConfig must also set 'allow_service_credential_mint' => false.

That last step matters: without it, if the per-session cache entry is missing or expired (token revoked, refresh not yet run, cache evicted), AccessToken::generateNewAccessToken() would fall through to minting a service (client_credentials) token from the global client_id/client_secret — and cache it under that user's session key. The next request would then silently run as the service account while believing it was acting as that user — exactly the identity-laundering hole the Stage 1.5 model closes. With allow_service_credential_mint=false set, a cache miss on a user-context config fails closed instead: ApiRequest::request() returns a non-success ApiResponse (no HTTP call is made), and the consumer should re-authenticate or run its own refresh flow rather than receive a token at all.

<?php

use SimplifiApi\ApiRequest;

// Per-session config for an authenticated user — set once after login/refresh writes the
// token into Redis under $sessionCustomKey.
$response = ApiRequest::request(
    ['method' => 'GET', 'url' => 'sales'],
    [
        'access_token' => [
            'store_as' => 'custom',
            'custom' => [
                'custom_key' => $sessionCustomKey,
                'get' => ['App\\Cache\\TokenStore', 'get'],
                'set' => ['App\\Cache\\TokenStore', 'set'],
                'del' => ['App\\Cache\\TokenStore', 'del'],
            ],
        ],
        'allow_service_credential_mint' => false,
    ]
);

Leave allow_service_credential_mint unset (defaults true) on the global/anonymous config used for client_credentials calls — there is no user identity to protect there, and that path is exactly what generateNewAccessToken() is for.

Development

composer run lint:check
composer run stan
composer run test

Docs

greghanton/simplifi-hq-oauth-api 适用场景与选型建议

greghanton/simplifi-hq-oauth-api 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 1.17k 次下载、GitHub Stars 达 0, 最近一次更新时间为 2020 年 05 月 25 日, 在 PHP 生态内属于活跃度较高的组件。

我们在过去多个企业项目中使用过 greghanton/simplifi-hq-oauth-api 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 greghanton/simplifi-hq-oauth-api 我们能提供哪些服务?
定制开发 / 二次开发

基于 greghanton/simplifi-hq-oauth-api 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 1.17k
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 20
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 2
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: proprietary
  • 更新时间: 2020-05-25