mirror of https://github.com/stashapp/stash.git
Add stash-box credentials validation (#2173)
This commit is contained in:
parent
90a4931bdc
commit
34aea876e8
|
@ -11,3 +11,10 @@ query Directory($path: String) {
|
|||
directories
|
||||
}
|
||||
}
|
||||
|
||||
query ValidateStashBox($input: StashBoxInput!) {
|
||||
validateStashBoxCredentials(input: $input) {
|
||||
valid
|
||||
status
|
||||
}
|
||||
}
|
||||
|
|
|
@ -136,6 +136,7 @@ type Query {
|
|||
"Desired collation locale. Determines the order of the directory result. eg. 'en-US', 'pt-BR', ..."
|
||||
locale: String = "en"
|
||||
): Directory!
|
||||
validateStashBoxCredentials(input: StashBoxInput!): StashBoxValidationResult!
|
||||
|
||||
# System status
|
||||
systemStatus: SystemStatus!
|
||||
|
|
|
@ -391,3 +391,8 @@ type StashConfig {
|
|||
input GenerateAPIKeyInput {
|
||||
clear: Boolean
|
||||
}
|
||||
|
||||
type StashBoxValidationResult {
|
||||
valid: Boolean!
|
||||
status: String!
|
||||
}
|
||||
|
|
|
@ -156,3 +156,9 @@ query FindSceneByID($id: ID!) {
|
|||
mutation SubmitFingerprint($input: FingerprintSubmission!) {
|
||||
submitFingerprint(input: $input)
|
||||
}
|
||||
|
||||
query Me {
|
||||
me {
|
||||
name
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,9 +2,12 @@ package api
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/stashapp/stash/pkg/manager/config"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/scraper/stashbox"
|
||||
"github.com/stashapp/stash/pkg/utils"
|
||||
"golang.org/x/text/collate"
|
||||
)
|
||||
|
@ -188,3 +191,38 @@ func makeConfigDefaultsResult() *models.ConfigDefaultSettingsResult {
|
|||
DeleteGenerated: &deleteGeneratedDefault,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *queryResolver) ValidateStashBoxCredentials(ctx context.Context, input models.StashBoxInput) (*models.StashBoxValidationResult, error) {
|
||||
client := stashbox.NewClient(models.StashBox{Endpoint: input.Endpoint, APIKey: input.APIKey}, r.txnManager)
|
||||
user, err := client.GetUser(ctx)
|
||||
|
||||
valid := user != nil && user.Me != nil
|
||||
var status string
|
||||
if valid {
|
||||
status = fmt.Sprintf("Successfully authenticated as %s", user.Me.Name)
|
||||
} else {
|
||||
switch {
|
||||
case strings.Contains(strings.ToLower(err.Error()), "doctype"):
|
||||
// Index file returned rather than graphql
|
||||
status = "Invalid endpoint"
|
||||
case strings.Contains(err.Error(), "request failed"):
|
||||
status = "No response from server"
|
||||
case strings.HasPrefix(err.Error(), "invalid character") ||
|
||||
strings.HasPrefix(err.Error(), "illegal base64 data") ||
|
||||
err.Error() == "unexpected end of JSON input" ||
|
||||
err.Error() == "token contains an invalid number of segments":
|
||||
status = "Malformed API key."
|
||||
case err.Error() == "" || err.Error() == "signature is invalid":
|
||||
status = "Invalid or expired API key."
|
||||
default:
|
||||
status = fmt.Sprintf("Unknown error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
result := models.StashBoxValidationResult{
|
||||
Valid: valid,
|
||||
Status: status,
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
|
|
@ -180,45 +180,32 @@ type FindSceneByID struct {
|
|||
type SubmitFingerprintPayload struct {
|
||||
SubmitFingerprint bool "json:\"submitFingerprint\" graphql:\"submitFingerprint\""
|
||||
}
|
||||
type Me struct {
|
||||
Me *struct {
|
||||
Name string "json:\"name\" graphql:\"name\""
|
||||
} "json:\"me\" graphql:\"me\""
|
||||
}
|
||||
|
||||
const FindSceneByFingerprintQuery = `query FindSceneByFingerprint ($fingerprint: FingerprintQueryInput!) {
|
||||
findSceneByFingerprint(fingerprint: $fingerprint) {
|
||||
... SceneFragment
|
||||
}
|
||||
}
|
||||
fragment FuzzyDateFragment on FuzzyDate {
|
||||
date
|
||||
accuracy
|
||||
}
|
||||
fragment SceneFragment on Scene {
|
||||
id
|
||||
title
|
||||
details
|
||||
fragment FingerprintFragment on Fingerprint {
|
||||
algorithm
|
||||
hash
|
||||
duration
|
||||
date
|
||||
urls {
|
||||
... URLFragment
|
||||
}
|
||||
images {
|
||||
... ImageFragment
|
||||
}
|
||||
studio {
|
||||
... StudioFragment
|
||||
}
|
||||
tags {
|
||||
... TagFragment
|
||||
}
|
||||
performers {
|
||||
... PerformerAppearanceFragment
|
||||
}
|
||||
fingerprints {
|
||||
... FingerprintFragment
|
||||
}
|
||||
}
|
||||
fragment URLFragment on URL {
|
||||
url
|
||||
type
|
||||
}
|
||||
fragment ImageFragment on Image {
|
||||
id
|
||||
url
|
||||
width
|
||||
height
|
||||
}
|
||||
fragment StudioFragment on Studio {
|
||||
name
|
||||
id
|
||||
|
@ -269,31 +256,49 @@ fragment PerformerFragment on Performer {
|
|||
... BodyModificationFragment
|
||||
}
|
||||
}
|
||||
fragment ImageFragment on Image {
|
||||
fragment BodyModificationFragment on BodyModification {
|
||||
location
|
||||
description
|
||||
}
|
||||
fragment SceneFragment on Scene {
|
||||
id
|
||||
url
|
||||
width
|
||||
height
|
||||
title
|
||||
details
|
||||
duration
|
||||
date
|
||||
urls {
|
||||
... URLFragment
|
||||
}
|
||||
images {
|
||||
... ImageFragment
|
||||
}
|
||||
studio {
|
||||
... StudioFragment
|
||||
}
|
||||
tags {
|
||||
... TagFragment
|
||||
}
|
||||
performers {
|
||||
... PerformerAppearanceFragment
|
||||
}
|
||||
fingerprints {
|
||||
... FingerprintFragment
|
||||
}
|
||||
}
|
||||
fragment TagFragment on Tag {
|
||||
name
|
||||
id
|
||||
}
|
||||
fragment FuzzyDateFragment on FuzzyDate {
|
||||
date
|
||||
accuracy
|
||||
}
|
||||
fragment MeasurementsFragment on Measurements {
|
||||
band_size
|
||||
cup_size
|
||||
waist
|
||||
hip
|
||||
}
|
||||
fragment BodyModificationFragment on BodyModification {
|
||||
location
|
||||
description
|
||||
}
|
||||
fragment FingerprintFragment on Fingerprint {
|
||||
algorithm
|
||||
hash
|
||||
duration
|
||||
}
|
||||
`
|
||||
|
||||
func (c *Client) FindSceneByFingerprint(ctx context.Context, fingerprint FingerprintQueryInput, httpRequestOptions ...client.HTTPRequestOption) (*FindSceneByFingerprint, error) {
|
||||
|
@ -314,12 +319,6 @@ const FindScenesByFullFingerprintsQuery = `query FindScenesByFullFingerprints ($
|
|||
... SceneFragment
|
||||
}
|
||||
}
|
||||
fragment ImageFragment on Image {
|
||||
id
|
||||
url
|
||||
width
|
||||
height
|
||||
}
|
||||
fragment StudioFragment on Studio {
|
||||
name
|
||||
id
|
||||
|
@ -330,10 +329,6 @@ fragment StudioFragment on Studio {
|
|||
... ImageFragment
|
||||
}
|
||||
}
|
||||
fragment TagFragment on Tag {
|
||||
name
|
||||
id
|
||||
}
|
||||
fragment PerformerFragment on Performer {
|
||||
id
|
||||
name
|
||||
|
@ -372,11 +367,14 @@ fragment FuzzyDateFragment on FuzzyDate {
|
|||
date
|
||||
accuracy
|
||||
}
|
||||
fragment MeasurementsFragment on Measurements {
|
||||
band_size
|
||||
cup_size
|
||||
waist
|
||||
hip
|
||||
fragment BodyModificationFragment on BodyModification {
|
||||
location
|
||||
description
|
||||
}
|
||||
fragment FingerprintFragment on Fingerprint {
|
||||
algorithm
|
||||
hash
|
||||
duration
|
||||
}
|
||||
fragment SceneFragment on Scene {
|
||||
id
|
||||
|
@ -403,24 +401,31 @@ fragment SceneFragment on Scene {
|
|||
... FingerprintFragment
|
||||
}
|
||||
}
|
||||
fragment URLFragment on URL {
|
||||
url
|
||||
type
|
||||
}
|
||||
fragment ImageFragment on Image {
|
||||
id
|
||||
url
|
||||
width
|
||||
height
|
||||
}
|
||||
fragment TagFragment on Tag {
|
||||
name
|
||||
id
|
||||
}
|
||||
fragment PerformerAppearanceFragment on PerformerAppearance {
|
||||
as
|
||||
performer {
|
||||
... PerformerFragment
|
||||
}
|
||||
}
|
||||
fragment BodyModificationFragment on BodyModification {
|
||||
location
|
||||
description
|
||||
}
|
||||
fragment FingerprintFragment on Fingerprint {
|
||||
algorithm
|
||||
hash
|
||||
duration
|
||||
}
|
||||
fragment URLFragment on URL {
|
||||
url
|
||||
type
|
||||
fragment MeasurementsFragment on Measurements {
|
||||
band_size
|
||||
cup_size
|
||||
waist
|
||||
hip
|
||||
}
|
||||
`
|
||||
|
||||
|
@ -452,18 +457,19 @@ fragment ImageFragment on Image {
|
|||
width
|
||||
height
|
||||
}
|
||||
fragment TagFragment on Tag {
|
||||
name
|
||||
id
|
||||
}
|
||||
fragment FuzzyDateFragment on FuzzyDate {
|
||||
date
|
||||
accuracy
|
||||
}
|
||||
fragment FingerprintFragment on Fingerprint {
|
||||
algorithm
|
||||
hash
|
||||
duration
|
||||
fragment MeasurementsFragment on Measurements {
|
||||
band_size
|
||||
cup_size
|
||||
waist
|
||||
hip
|
||||
}
|
||||
fragment BodyModificationFragment on BodyModification {
|
||||
location
|
||||
description
|
||||
}
|
||||
fragment SceneFragment on Scene {
|
||||
id
|
||||
|
@ -500,6 +506,10 @@ fragment StudioFragment on Studio {
|
|||
... ImageFragment
|
||||
}
|
||||
}
|
||||
fragment TagFragment on Tag {
|
||||
name
|
||||
id
|
||||
}
|
||||
fragment PerformerAppearanceFragment on PerformerAppearance {
|
||||
as
|
||||
performer {
|
||||
|
@ -540,15 +550,10 @@ fragment PerformerFragment on Performer {
|
|||
... BodyModificationFragment
|
||||
}
|
||||
}
|
||||
fragment MeasurementsFragment on Measurements {
|
||||
band_size
|
||||
cup_size
|
||||
waist
|
||||
hip
|
||||
}
|
||||
fragment BodyModificationFragment on BodyModification {
|
||||
location
|
||||
description
|
||||
fragment FingerprintFragment on Fingerprint {
|
||||
algorithm
|
||||
hash
|
||||
duration
|
||||
}
|
||||
`
|
||||
|
||||
|
@ -570,30 +575,6 @@ const SearchPerformerQuery = `query SearchPerformer ($term: String!) {
|
|||
... PerformerFragment
|
||||
}
|
||||
}
|
||||
fragment URLFragment on URL {
|
||||
url
|
||||
type
|
||||
}
|
||||
fragment ImageFragment on Image {
|
||||
id
|
||||
url
|
||||
width
|
||||
height
|
||||
}
|
||||
fragment FuzzyDateFragment on FuzzyDate {
|
||||
date
|
||||
accuracy
|
||||
}
|
||||
fragment MeasurementsFragment on Measurements {
|
||||
band_size
|
||||
cup_size
|
||||
waist
|
||||
hip
|
||||
}
|
||||
fragment BodyModificationFragment on BodyModification {
|
||||
location
|
||||
description
|
||||
}
|
||||
fragment PerformerFragment on Performer {
|
||||
id
|
||||
name
|
||||
|
@ -628,6 +609,30 @@ fragment PerformerFragment on Performer {
|
|||
... BodyModificationFragment
|
||||
}
|
||||
}
|
||||
fragment URLFragment on URL {
|
||||
url
|
||||
type
|
||||
}
|
||||
fragment ImageFragment on Image {
|
||||
id
|
||||
url
|
||||
width
|
||||
height
|
||||
}
|
||||
fragment FuzzyDateFragment on FuzzyDate {
|
||||
date
|
||||
accuracy
|
||||
}
|
||||
fragment MeasurementsFragment on Measurements {
|
||||
band_size
|
||||
cup_size
|
||||
waist
|
||||
hip
|
||||
}
|
||||
fragment BodyModificationFragment on BodyModification {
|
||||
location
|
||||
description
|
||||
}
|
||||
`
|
||||
|
||||
func (c *Client) SearchPerformer(ctx context.Context, term string, httpRequestOptions ...client.HTTPRequestOption) (*SearchPerformer, error) {
|
||||
|
@ -732,6 +737,35 @@ fragment ImageFragment on Image {
|
|||
width
|
||||
height
|
||||
}
|
||||
fragment TagFragment on Tag {
|
||||
name
|
||||
id
|
||||
}
|
||||
fragment PerformerAppearanceFragment on PerformerAppearance {
|
||||
as
|
||||
performer {
|
||||
... PerformerFragment
|
||||
}
|
||||
}
|
||||
fragment FuzzyDateFragment on FuzzyDate {
|
||||
date
|
||||
accuracy
|
||||
}
|
||||
fragment MeasurementsFragment on Measurements {
|
||||
band_size
|
||||
cup_size
|
||||
waist
|
||||
hip
|
||||
}
|
||||
fragment FingerprintFragment on Fingerprint {
|
||||
algorithm
|
||||
hash
|
||||
duration
|
||||
}
|
||||
fragment URLFragment on URL {
|
||||
url
|
||||
type
|
||||
}
|
||||
fragment StudioFragment on Studio {
|
||||
name
|
||||
id
|
||||
|
@ -742,10 +776,6 @@ fragment StudioFragment on Studio {
|
|||
... ImageFragment
|
||||
}
|
||||
}
|
||||
fragment TagFragment on Tag {
|
||||
name
|
||||
id
|
||||
}
|
||||
fragment PerformerFragment on Performer {
|
||||
id
|
||||
name
|
||||
|
@ -780,16 +810,9 @@ fragment PerformerFragment on Performer {
|
|||
... BodyModificationFragment
|
||||
}
|
||||
}
|
||||
fragment MeasurementsFragment on Measurements {
|
||||
band_size
|
||||
cup_size
|
||||
waist
|
||||
hip
|
||||
}
|
||||
fragment FingerprintFragment on Fingerprint {
|
||||
algorithm
|
||||
hash
|
||||
duration
|
||||
fragment BodyModificationFragment on BodyModification {
|
||||
location
|
||||
description
|
||||
}
|
||||
fragment SceneFragment on Scene {
|
||||
id
|
||||
|
@ -816,24 +839,6 @@ fragment SceneFragment on Scene {
|
|||
... FingerprintFragment
|
||||
}
|
||||
}
|
||||
fragment URLFragment on URL {
|
||||
url
|
||||
type
|
||||
}
|
||||
fragment BodyModificationFragment on BodyModification {
|
||||
location
|
||||
description
|
||||
}
|
||||
fragment PerformerAppearanceFragment on PerformerAppearance {
|
||||
as
|
||||
performer {
|
||||
... PerformerFragment
|
||||
}
|
||||
}
|
||||
fragment FuzzyDateFragment on FuzzyDate {
|
||||
date
|
||||
accuracy
|
||||
}
|
||||
`
|
||||
|
||||
func (c *Client) FindSceneByID(ctx context.Context, id string, httpRequestOptions ...client.HTTPRequestOption) (*FindSceneByID, error) {
|
||||
|
@ -866,3 +871,21 @@ func (c *Client) SubmitFingerprint(ctx context.Context, input FingerprintSubmiss
|
|||
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
const MeQuery = `query Me {
|
||||
me {
|
||||
name
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
func (c *Client) Me(ctx context.Context, httpRequestOptions ...client.HTTPRequestOption) (*Me, error) {
|
||||
vars := map[string]interface{}{}
|
||||
|
||||
var res Me
|
||||
if err := c.Client.Post(ctx, MeQuery, &res, vars, httpRequestOptions...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &res, nil
|
||||
}
|
||||
|
|
|
@ -753,3 +753,7 @@ func (c Client) FindStashBoxPerformerByName(ctx context.Context, name string) (*
|
|||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (c Client) GetUser(ctx context.Context) (*graphql.Me, error) {
|
||||
return c.client.Me(ctx)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
### 🎨 Improvements
|
||||
Show counts on list tabs in Performer, Studio and Tag pages. ([#2169](https://github.com/stashapp/stash/pull/2169))
|
||||
* Add button to test credentials when adding/editing stash-box endpoints. ([#2173](https://github.com/stashapp/stash/pull/2173))
|
||||
* Show counts on list tabs in Performer, Studio and Tag pages. ([#2169](https://github.com/stashapp/stash/pull/2169))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
* Generate sprites for short video files. ([#2167](https://github.com/stashapp/stash/pull/2167))
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState } from "react";
|
||||
import React, { useRef, useState } from "react";
|
||||
import { Button, Form } from "react-bootstrap";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import { SettingSection } from "./SettingSection";
|
||||
|
@ -12,6 +12,24 @@ export interface IStashBoxModal {
|
|||
|
||||
export const StashBoxModal: React.FC<IStashBoxModal> = ({ value, close }) => {
|
||||
const intl = useIntl();
|
||||
const endpoint = useRef<HTMLInputElement | null>(null);
|
||||
const apiKey = useRef<HTMLInputElement | null>(null);
|
||||
|
||||
const [validate, { data, loading }] = GQL.useValidateStashBoxLazyQuery({
|
||||
fetchPolicy: "network-only",
|
||||
});
|
||||
|
||||
const handleValidate = () => {
|
||||
validate({
|
||||
variables: {
|
||||
input: {
|
||||
endpoint: endpoint.current?.value ?? "",
|
||||
api_key: apiKey.current?.value ?? "",
|
||||
name: "test",
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<SettingModal<GQL.StashBoxInput>
|
||||
|
@ -52,6 +70,7 @@ export const StashBoxModal: React.FC<IStashBoxModal> = ({ value, close }) => {
|
|||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setValue({ ...v!, endpoint: e.currentTarget.value.trim() })
|
||||
}
|
||||
ref={endpoint}
|
||||
/>
|
||||
</Form.Group>
|
||||
|
||||
|
@ -71,8 +90,30 @@ export const StashBoxModal: React.FC<IStashBoxModal> = ({ value, close }) => {
|
|||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setValue({ ...v!, api_key: e.currentTarget.value.trim() })
|
||||
}
|
||||
ref={apiKey}
|
||||
/>
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group>
|
||||
<Button
|
||||
disabled={loading}
|
||||
onClick={handleValidate}
|
||||
className="mr-3"
|
||||
>
|
||||
Test Credentials
|
||||
</Button>
|
||||
{data && (
|
||||
<b
|
||||
className={
|
||||
data.validateStashBoxCredentials?.valid
|
||||
? "text-success"
|
||||
: "text-danger"
|
||||
}
|
||||
>
|
||||
{data.validateStashBoxCredentials?.status}
|
||||
</b>
|
||||
)}
|
||||
</Form.Group>
|
||||
</>
|
||||
)}
|
||||
close={close}
|
||||
|
|
Loading…
Reference in New Issue