Add stash-box credentials validation (#2173)

This commit is contained in:
InfiniteTF 2022-01-04 04:20:31 +01:00 committed by GitHub
parent 90a4931bdc
commit 34aea876e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 268 additions and 142 deletions

View File

@ -11,3 +11,10 @@ query Directory($path: String) {
directories
}
}
query ValidateStashBox($input: StashBoxInput!) {
validateStashBoxCredentials(input: $input) {
valid
status
}
}

View File

@ -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!

View File

@ -391,3 +391,8 @@ type StashConfig {
input GenerateAPIKeyInput {
clear: Boolean
}
type StashBoxValidationResult {
valid: Boolean!
status: String!
}

View File

@ -156,3 +156,9 @@ query FindSceneByID($id: ID!) {
mutation SubmitFingerprint($input: FingerprintSubmission!) {
submitFingerprint(input: $input)
}
query Me {
me {
name
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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))

View File

@ -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}