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
9 changed files with 268 additions and 142 deletions

View File

@@ -11,3 +11,10 @@ query Directory($path: String) {
directories 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', ..." "Desired collation locale. Determines the order of the directory result. eg. 'en-US', 'pt-BR', ..."
locale: String = "en" locale: String = "en"
): Directory! ): Directory!
validateStashBoxCredentials(input: StashBoxInput!): StashBoxValidationResult!
# System status # System status
systemStatus: SystemStatus! systemStatus: SystemStatus!

View File

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

View File

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

View File

@@ -2,9 +2,12 @@ package api
import ( import (
"context" "context"
"fmt"
"strings"
"github.com/stashapp/stash/pkg/manager/config" "github.com/stashapp/stash/pkg/manager/config"
"github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/scraper/stashbox"
"github.com/stashapp/stash/pkg/utils" "github.com/stashapp/stash/pkg/utils"
"golang.org/x/text/collate" "golang.org/x/text/collate"
) )
@@ -188,3 +191,38 @@ func makeConfigDefaultsResult() *models.ConfigDefaultSettingsResult {
DeleteGenerated: &deleteGeneratedDefault, 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 { type SubmitFingerprintPayload struct {
SubmitFingerprint bool "json:\"submitFingerprint\" graphql:\"submitFingerprint\"" 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!) { const FindSceneByFingerprintQuery = `query FindSceneByFingerprint ($fingerprint: FingerprintQueryInput!) {
findSceneByFingerprint(fingerprint: $fingerprint) { findSceneByFingerprint(fingerprint: $fingerprint) {
... SceneFragment ... SceneFragment
} }
} }
fragment FuzzyDateFragment on FuzzyDate { fragment FingerprintFragment on Fingerprint {
date algorithm
accuracy hash
}
fragment SceneFragment on Scene {
id
title
details
duration duration
date
urls {
... URLFragment
}
images {
... ImageFragment
}
studio {
... StudioFragment
}
tags {
... TagFragment
}
performers {
... PerformerAppearanceFragment
}
fingerprints {
... FingerprintFragment
}
} }
fragment URLFragment on URL { fragment URLFragment on URL {
url url
type type
} }
fragment ImageFragment on Image {
id
url
width
height
}
fragment StudioFragment on Studio { fragment StudioFragment on Studio {
name name
id id
@@ -269,31 +256,49 @@ fragment PerformerFragment on Performer {
... BodyModificationFragment ... BodyModificationFragment
} }
} }
fragment ImageFragment on Image { fragment BodyModificationFragment on BodyModification {
location
description
}
fragment SceneFragment on Scene {
id id
url title
width details
height duration
date
urls {
... URLFragment
}
images {
... ImageFragment
}
studio {
... StudioFragment
}
tags {
... TagFragment
}
performers {
... PerformerAppearanceFragment
}
fingerprints {
... FingerprintFragment
}
} }
fragment TagFragment on Tag { fragment TagFragment on Tag {
name name
id id
} }
fragment FuzzyDateFragment on FuzzyDate {
date
accuracy
}
fragment MeasurementsFragment on Measurements { fragment MeasurementsFragment on Measurements {
band_size band_size
cup_size cup_size
waist waist
hip 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) { func (c *Client) FindSceneByFingerprint(ctx context.Context, fingerprint FingerprintQueryInput, httpRequestOptions ...client.HTTPRequestOption) (*FindSceneByFingerprint, error) {
@@ -314,12 +319,6 @@ const FindScenesByFullFingerprintsQuery = `query FindScenesByFullFingerprints ($
... SceneFragment ... SceneFragment
} }
} }
fragment ImageFragment on Image {
id
url
width
height
}
fragment StudioFragment on Studio { fragment StudioFragment on Studio {
name name
id id
@@ -330,10 +329,6 @@ fragment StudioFragment on Studio {
... ImageFragment ... ImageFragment
} }
} }
fragment TagFragment on Tag {
name
id
}
fragment PerformerFragment on Performer { fragment PerformerFragment on Performer {
id id
name name
@@ -372,11 +367,14 @@ fragment FuzzyDateFragment on FuzzyDate {
date date
accuracy accuracy
} }
fragment MeasurementsFragment on Measurements { fragment BodyModificationFragment on BodyModification {
band_size location
cup_size description
waist }
hip fragment FingerprintFragment on Fingerprint {
algorithm
hash
duration
} }
fragment SceneFragment on Scene { fragment SceneFragment on Scene {
id id
@@ -403,24 +401,31 @@ fragment SceneFragment on Scene {
... FingerprintFragment ... 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 { fragment PerformerAppearanceFragment on PerformerAppearance {
as as
performer { performer {
... PerformerFragment ... PerformerFragment
} }
} }
fragment BodyModificationFragment on BodyModification { fragment MeasurementsFragment on Measurements {
location band_size
description cup_size
} waist
fragment FingerprintFragment on Fingerprint { hip
algorithm
hash
duration
}
fragment URLFragment on URL {
url
type
} }
` `
@@ -452,18 +457,19 @@ fragment ImageFragment on Image {
width width
height height
} }
fragment TagFragment on Tag {
name
id
}
fragment FuzzyDateFragment on FuzzyDate { fragment FuzzyDateFragment on FuzzyDate {
date date
accuracy accuracy
} }
fragment FingerprintFragment on Fingerprint { fragment MeasurementsFragment on Measurements {
algorithm band_size
hash cup_size
duration waist
hip
}
fragment BodyModificationFragment on BodyModification {
location
description
} }
fragment SceneFragment on Scene { fragment SceneFragment on Scene {
id id
@@ -500,6 +506,10 @@ fragment StudioFragment on Studio {
... ImageFragment ... ImageFragment
} }
} }
fragment TagFragment on Tag {
name
id
}
fragment PerformerAppearanceFragment on PerformerAppearance { fragment PerformerAppearanceFragment on PerformerAppearance {
as as
performer { performer {
@@ -540,15 +550,10 @@ fragment PerformerFragment on Performer {
... BodyModificationFragment ... BodyModificationFragment
} }
} }
fragment MeasurementsFragment on Measurements { fragment FingerprintFragment on Fingerprint {
band_size algorithm
cup_size hash
waist duration
hip
}
fragment BodyModificationFragment on BodyModification {
location
description
} }
` `
@@ -570,30 +575,6 @@ const SearchPerformerQuery = `query SearchPerformer ($term: String!) {
... PerformerFragment ... 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 { fragment PerformerFragment on Performer {
id id
name name
@@ -628,6 +609,30 @@ fragment PerformerFragment on Performer {
... BodyModificationFragment ... 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) { func (c *Client) SearchPerformer(ctx context.Context, term string, httpRequestOptions ...client.HTTPRequestOption) (*SearchPerformer, error) {
@@ -732,6 +737,35 @@ fragment ImageFragment on Image {
width width
height 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 { fragment StudioFragment on Studio {
name name
id id
@@ -742,10 +776,6 @@ fragment StudioFragment on Studio {
... ImageFragment ... ImageFragment
} }
} }
fragment TagFragment on Tag {
name
id
}
fragment PerformerFragment on Performer { fragment PerformerFragment on Performer {
id id
name name
@@ -780,16 +810,9 @@ fragment PerformerFragment on Performer {
... BodyModificationFragment ... BodyModificationFragment
} }
} }
fragment MeasurementsFragment on Measurements { fragment BodyModificationFragment on BodyModification {
band_size location
cup_size description
waist
hip
}
fragment FingerprintFragment on Fingerprint {
algorithm
hash
duration
} }
fragment SceneFragment on Scene { fragment SceneFragment on Scene {
id id
@@ -816,24 +839,6 @@ fragment SceneFragment on Scene {
... FingerprintFragment ... 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) { 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 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 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 ### 🎨 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 ### 🐛 Bug fixes
* Generate sprites for short video files. ([#2167](https://github.com/stashapp/stash/pull/2167)) * 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 { Button, Form } from "react-bootstrap";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import { SettingSection } from "./SettingSection"; import { SettingSection } from "./SettingSection";
@@ -12,6 +12,24 @@ export interface IStashBoxModal {
export const StashBoxModal: React.FC<IStashBoxModal> = ({ value, close }) => { export const StashBoxModal: React.FC<IStashBoxModal> = ({ value, close }) => {
const intl = useIntl(); 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 ( return (
<SettingModal<GQL.StashBoxInput> <SettingModal<GQL.StashBoxInput>
@@ -52,6 +70,7 @@ export const StashBoxModal: React.FC<IStashBoxModal> = ({ value, close }) => {
onChange={(e: React.ChangeEvent<HTMLInputElement>) => onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setValue({ ...v!, endpoint: e.currentTarget.value.trim() }) setValue({ ...v!, endpoint: e.currentTarget.value.trim() })
} }
ref={endpoint}
/> />
</Form.Group> </Form.Group>
@@ -71,8 +90,30 @@ export const StashBoxModal: React.FC<IStashBoxModal> = ({ value, close }) => {
onChange={(e: React.ChangeEvent<HTMLInputElement>) => onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setValue({ ...v!, api_key: e.currentTarget.value.trim() }) setValue({ ...v!, api_key: e.currentTarget.value.trim() })
} }
ref={apiKey}
/> />
</Form.Group> </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} close={close}